Setting up NGINX Reverse-Proxy, Cloudflare Let’s Encrypt SSL for NextCloud, DDNS on Freenas 11.3-U1Jail

Nginx Reverse Proxy on Freenas 11.3

Provide Extra layer of Abstraction to your hosted services!

By Fahad Usman

What is a Reverse Proxy?

reverse proxy is a type of proxy server that retrieves resources on behalf of a client from one or more servers. These resources are then returned to the client, appearing as if they originated from the proxy server itself.

This provides an extra layer of abstraction to your internally hosted servers/applications.


“Reverse proxy creates a single point of access to your internally hosted file transfer/media servers and simplifies access control to your valuable data and applications”

In this guide, I will take you through step by step how to set it up using nginx.


I want to create one stop shop for my internal applications access such as plex media server, medusa, transmission, nextcloud etc. etc.

I want to access NextCloud outside my home but everything else stays private and aren’t accessible outside my home.

I want to use SSL for everything (internal and external) to encrypt my traffic.

I want to use stronger Diffie Hellmen key (4096) instead of the default 2048.

Step 0 – Getting a domain and update its DNS servers to Cloudflare:

Register a domain and update its DNS server. I am using cloudflare DNS servers. Once you have this then goto duckdns and register for a service and add a server. For this example, I am going to call it This will be pointing to my home WAN IP ADDRESS.

You will now need to create a CNAME record in cloudflare that will point to This record needs to be DNS Only.

So here is the overall picture, what we’re trying to achieve.

reverse proxy setup

If you want to expose multiple services then you will need to create multiple sub-domains and point them to the duck dns server via CNAME DNS ONLY as show below:

Multiple domains

“DuckDNS will help us to keep track of our WAN IP address.”

Step 1 – Create a jail:

You can create it using Freenas GUI but this time, i will create it using command line. Just ssh into your box using root.

Now run the following command:

iocage create -n reverse-proxy -r 11.3-RELEASE ip4_addr="vnet0|" defaultrouter="" vnet="on" allow_raw_sockets="1" boot="on"

What this command will do is:

  • iocage create: iocage is the generic command and when combined with create, it instructs the Freenas to create a new iocage jail
  • -n reverse-proxy: gives the jail the name ‘reverse-proxy’
  • -r 11.3-RELEASE: specifies the release of FreeBSD to be installed in the jail.
  • ip4_addr="vnet0|": specifies the networking including an IP/mask for the jail, and the interface to use, vnet0. To keep it simple, just specify the IP to be on the same subnet as your router.
  • defaultrouter="": specifies the router for your network, change this as is relevant for you
  • vnet="on": enables the vnet interface
  • allow_raw_sockets="1": enables raw sockets, which enables the use of programs such as traceroute and ping within the jail, as well as interactions with various network subsystems
  • boot="on": enables the jail to be auto-started at boot time.
    More detail on the parameters that can be used to configure a jail on creation can be found in the man page for iocage

Now if you do:

iocage list

You should see reverse-proxy in the list.

Just run:

iocage console reverse-proxy

To get into the jail console and run:

pkg update
pkg upgrade

Step 2 – Install NGINX:

Installing the packages are a piece of cake. Just run:

pkg install nginx python

Enable nginx so that the service begins when the jail is started

sysrc nginx_enable=yes

Step 3 – SSL/TLS:

I spent hours trying to figure out how to get my SSL certificate and private key from cloudflare. The process is pretty straight forward actually. We will need to install certbot which makes it super easy to obtain the certificate and the key from letsencrypt. Install it by:

pkg install py37-certbot openssl

Now, get your API key from cloudflare by logging into your cloudflare account and then going in your profile and then Global API key. This is very important to keep it safe. Once done then create a file that is required by the certbot to verify that you own the domain in order to get you the certificate and the private key.

mkdir /root/.secrets
cd /root/.secrets
vi cloudflare.ini

and insert the following information:

dns_cloudflare_email = "[email protected]"
dns_cloudflare_api_key = "your_global_API_KEY_HERE"

save and exit.

Change permissions on the file and the .secret folder so that only root can read this file and folder by:

chmod 400 -R .secret

You need to install cloudflare extension/package in addition to certbot so that you could use it easily. You can search for it using:

pkg search certbot

I choose to install:


Now run the process of getting the certificate by:

certbot certonly --dns-cloudflare

Enter your domain name e.g. * and the path to the cloudflare.ini file you just created when prompted and it will do its thing and you will get the certificate, key etc.

Now this is valid for a certain period and you would like to automate the renewal process by:

crontab -e

and insert the following:

0 0,12 * * * /usr/local/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && /usr/local/bin/certbot renew --quiet --deploy-hook "/usr/sbin/service nginx reload"

Save and Exit (Ctrl + X), and the cron job should be configured. This command will attempt to renew the certificate at midnight and noon every day.

You can test it to see if all goes to plan by ‘dry run’:

certbot renew --dry-run

Step 4 – Generate your own strong and unique Diffie-Hellman (DH) Key:

I wanted to create the stronger key of 4096 instead of the default 2048. you can generate using:

cd /etc/ssl/
openssl dhparam -out dhparams.pem 4096


Step 5 –  Configure NGINX 

Finally we can configure the NGINX. We need 3 things to specify:

where our certificates, keys and chain.pem are stored in ssl parameters.

Then we need to specify proxy parameters.

Then we need to specify each domain server blocks i.e. where they live in the network.

in case you wish to restrict access to a server externally i.e. only accessible from LAN/inside your home then create rules for that in internal_rules.conf.

NGINX installation comes with the snippets folder. So we will make use of this to store our config files. But we will have to create a folder which will hold server block information per service.

mkdir /usr/local/etc/nginx/vdomains

Create Certificates/Keys Configuration:

cd /usr/local/etc/nginx/snippets

and paste the following:

# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
ssl_certificate /usr/local/etc/letsencrypt/live/;
ssl_certificate_key /usr/local/etc/letsencrypt/live/;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /usr/local/etc/letsencrypt/live/;

replace with your own domain and save and close.

Create SSL Configuration for SSL parameters:

vi ssl_common.conf
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
# Disable SSLv2 and SSLv3 (BEAST and POODLE attacks)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# Enable our strong DH Key
ssl_dhparam /etc/ssl/dh4096.pem;
# Cipher-list for PFS.
ssl_ecdh_curve secp384r1;
# Requires nginx >= 1.1.0
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Requires nginx >= 1.5.9
ssl_stapling on;
# Requires nginx >= 1.3.7
ssl_stapling_verify on;
# Requires nginx => 1.3.7
resolver valid=300s;
resolver_timeout 5s;
# HSTS Support
add_header Strict-Transport-Security "max-age=63072000;includeSubdomains; preload";



Change IP address of your own resolver. I use pihole so I added that. You could add your router’s IP address here.

Create Proxy Configuration:

vi proxy_params.conf

and paste:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_hide_header 'x-frame-options';
proxy_hide_header 'X-Content-Type-Options';
proxy_hide_header 'X-Download-Options';
proxy_http_version 1.1;


Default NGINX.conf

We now need to change the default nginx.conf. Make sure you copy it first to take a backup.

cp /usr/local/etc/nginx/nginx.conf /usr/local/etc/nginx/nginx.conf.bak
vi nginx.conf

and paste:

worker_processes  1;

events {
    worker_connections  1024;

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    # Redirect all HTTP traffic to HTTPS
    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        return 301 https://$host$request_uri;

    # Import server blocks for all subdomains
    include "vdomains/*.conf";

Now we just need to create server blocks and we’re done within the vdomains directory.

Server Block Config

I am assuming you want to access nextcloud outside your home network. So I will be creating its server block first. I am also assuming that you created CNAME of and pointed to DUCK DNS in Step 0.

cd vdomains

and paste:

server {
        listen 443 ssl http2;
        access_log /var/log/nginx/cloud.access.log;
        error_log /var/log/nginx/cloud.error.log;
        include snippets/;
        include snippets/ssl_common.conf;
        location / {
                include snippets/proxy_params.conf;




Notice proxy_pass with https. This is because I was using self signed certs internally when I setup the NextCloud previously.

You will need to create separate config files for every service you want to access externally in the same folder.

save and exit. Reload the NGINX by


system nginx reload

And try going to and it should load up the nextcloud login page.

However you will need to fix some more config for nextcloud to work properly.

Fixing the Nextcloud for Reverse Proxy:

First, you will now need to add your domain to trusted domains.

Trusted domains are used by Nextcloud to prevent Host Header Poisoning. You need to specify every domain at which your Nextcloud can be accessed.

This means if you have Nextcloud installed at “” and also want it to be accessible at “ 467” you’d need to modify the trusted_domains entry in your config/config.php. The initial config would look like the following:

  'trusted_domains' =>
  array (
    0 => '',

To add a new domain just add new entries by appending a new item to the PHP array:

  'trusted_domains' =>
  array (
    0 => '',
    1 => '',

If you use an environment where your IP address can change multiple times it is recommended to configure your DNS server in a way to resolve to the IP instead of accessing the IP address manually.

The second thing you need to add here is reverse proxy configuration. just paste the following after the ‘trusted_domains’

 'trusted_proxies' =>
  array (
    0 => '',
    1 => '',
  'forwarded-for-headers' =>
  array (

Save and close. Restart nginx in NextCloud jail to make sure your changes are registered.

Step 6 –  Creating local Server Configs

This is the last step. Suppose you have plex running internally and you wish to access it internally only. 

For this, you will need to create some rules within the snippets directory.

vi snippets/internal-access-rules.conf

And paste the following:

deny all;

Make sure you have a subdomain i.e. pointing to the same duckdnsserver you created in the step 0.

The only change you need in the server block is to add internal-access-rules.conf. The full plex.home.conf will look like like:

vi plex.home.conf

Paste the following:

server {
        listen 443 ssl http2;
        access_log /var/log/nginx/plex.access.log;
        error_log /var/log/nginx/plex.error.log;
        include snippets/;
        include snippets/ssl_common.conf;
        location / {
                include snippets/proxy_params.conf;
                include snippets/internal-access-rules.conf;
                proxy_pass "";




Restart nginx and goto the browser and type:

It should load up. But if you’re not on your home wifi. It will say “Forbidden

Step 7 –  Router Port Forwarding and Point your DNS to Reverse-Proxy Jail

This is the last step. I am not going to show you how to do port forwarding because every router is different. So you need to point both port 80 and 443 to this Reverse Proxy jail. 

You could also add local DNS in pihole if you’re running your own DNS server.

That’s it!!

Bonus Step – Setup DuckDNS update in FREENAS

This is just to keep your duckdns server updated about your current IP address. You need to keep telling them about your current IP address via CRON job in FreeNas.

Click Tasks -> Cron Jobs -> Add

Give a Description e.g. Duck DNS

Enter the command: /usr/local/bin/curl ""

I selected an hourly schedule.

That’s it!

This Post Has 7 Comments

  1. I’m getting stuck on step 3. When I try to do a dry run of the certbot renewal, I get this error:

    1 renew failure(s), 0 parse failure(s)

    – The following errors were reported by the server:

    Type: unauthorized
    Detail: During secondary validation: Incorrect TXT record
    “-d1dHnpyPcgm7MPmSk2Pdm1iYJLHWyh1me_tkAgCv_M” found at

    To fix these errors, please make sure that your domain name was
    entered correctly and the DNS A/AAAA record(s) for that domain
    contain(s) the right IP address.

    I don’t have any A/AAAA DNS records on Cloudflare, only the CNAME records created earlier in this guide. What should I do?

    1. re-read the guide slowly 😉 you need to have a domain registered. If you’re using cloudflare as your dns provider then create a CNAME DNS ONLY and point it to your Public IP. If you don’t have a static public ip address then you can use duckdns service.

  2. I get the following error:

    [email protected]:~/.secrets # chmod 400 -R .secret
    chmod: -R: No such file or directory

    What am I missing?

    1. Also, here is what my cloudflare.ini looks like

      [email protected]:~/.secrets # vi cloudflare.ini

      dns_cloudflare_email = “[email protected]
      dns_cloudflare_api_key = “6e6345jhwedfjhbe3ti34089dc9d5”

      [email protected]:~/.secrets #

    2. you need to mention the complete path…. chmod 400 -R /root/.secret if thats where the directory is… otherwise find out the full path by pwd command

      1. or double-check if the chmod command has worked by running the ls -la command. then look at the permissions

  3. Like!! I blog frequently and I really thank you for your content. The article has truly peaked my interest.

Leave a Reply

Close Menu