
By Fahad Usman
What is a Reverse Proxy?
a 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.
Requirements:
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 myserver.duckdns.org
. 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 myserver.duckdns.org
. This record needs to be DNS Only
.
So here is the overall picture, what we’re trying to achieve.

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:

“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|192.168.0.5/24" defaultrouter="192.168.0.1" vnet="on" allow_raw_sockets="1" boot="on"
What this command will do is:
iocage create
:iocage
is the generic command and when combined withcreate
, it instructs the Freenas to create a newiocage 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|192.168.0.5/24"
: 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 samesubnet
as your router.defaultrouter="192.168.0.1"
: specifies the router for your network, change this as is relevant for youvnet="on"
: enables the vnet interfaceallow_raw_sockets="1"
: enables raw sockets, which enables the use of programs such astraceroute
andping
within the jail, as well as interactions with various network subsystemsboot="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]_used_to_signup_to_cloudflare.com" 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:
py37-certbot-dns-cloudflare
Now run the process of getting the certificate by:
certbot certonly --dns-cloudflare
Enter your domain name e.g. *.example.com 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 vi example.com.cert.conf
and paste the following:
# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate ssl_certificate /usr/local/etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /usr/local/etc/letsencrypt/live/example.com/privkey.pem; # verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /usr/local/etc/letsencrypt/live/example.com/chain.pem;
replace example.com 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_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-A ES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES12 8-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; 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 192.168.0.100 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 cloud.example.com and pointed to DUCK DNS in Step 0.
cd vdomains
vi cloud.example.com
and paste:
server { listen 443 ssl http2; server_name cloud.example.com; access_log /var/log/nginx/cloud.access.log; error_log /var/log/nginx/cloud.error.log; include snippets/example.com.cert.conf; include snippets/ssl_common.conf; location / { include snippets/proxy_params.conf; proxy_pass https://192.168.0.2; } }
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 cloud.example.com 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 “192.168.0.2” and also want it to be accessible at “cloud.example.com 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 => '192.168.0.2',
),
To add a new domain just add new entries by appending a new item to the PHP array:
'trusted_domains' =>
array (
0 => '192.168.0.2',
1 => 'cloud.example.com',
),
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 => '192.168.0.5',
1 => '127.0.0.1',
),
'forwarded-for-headers' =>
array (
0 => 'HTTP_X_FORWARDED_FOR',
),
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:
allow 192.168.0.0/24;
deny all;
Make sure you have a subdomain i.e. plex.example.com 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;
server_name plex.example.com;
access_log /var/log/nginx/plex.access.log;
error_log /var/log/nginx/plex.error.log;
include snippets/example.com.cert.conf;
include snippets/ssl_common.conf;
location / {
include snippets/proxy_params.conf;
include snippets/internal-access-rules.conf;
proxy_pass "http://192.168.0.4:32400";
}
}
Restart nginx and goto the browser and type:
plex.example.com
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 "https://www.duckdns.org/update?domains=your_own_server_name_here&token=your-own-token-from-your_account_here&IP="
I selected an hourly schedule
.
That’s it!
Kyle Adler
23 Apr 2020I’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)
IMPORTANT NOTES:
– The following errors were reported by the server:
Domain: xatnys.com
Type: unauthorized
Detail: During secondary validation: Incorrect TXT record
“-d1dHnpyPcgm7MPmSk2Pdm1iYJLHWyh1me_tkAgCv_M” found at
_acme-challenge.xatnys.com
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?
fahadshery
27 Jul 2020re-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.
Ed
13 May 2020I get the following error:
[email protected]:~/.secrets # chmod 400 -R .secret
chmod: -R: No such file or directory
What am I missing?
Ed
Ed
13 May 2020Also, 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 #
fahadshery
27 Jul 2020you 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 commandfahadshery
27 Jul 2020or double-check if the chmod command has worked by running the
ls -la
command. then look at the permissionsปั้มไลค์
30 May 2020Like!! I blog frequently and I really thank you for your content. The article has truly peaked my interest.