Lire dans une autre langue : Français 🇫🇷, English 🇬🇧.
- What? Why?
- Install Merlin on the router
- Activate SSH and JFFS partition
- Install Entware
- Setup OVH DynHost on the router
- Install nginx
- Set up nginx
- Get Let's Encrypt certificate
- Conclusion
- Sources
- Bonuses
A reverse proxy is a small server that provides access to the user interfaces behind it, for example: camera web interfaces, multimedia servers, Nas, self-hosted calendar or email, etc. The goal is to access resources from the outside, without having to use a VPN. VPN and reverse proxy are not mutually exclusive as the proxy really is useful for web interfaces. In addition, the VPN allows increased security, when using public wifi for instance.
The reverse proxy can be secure. You just have to use a certificate, the connection will be encrypted between the external computer and the proxy. And with Let's Encrypt, it is possible to have a free certificate recognized by browsers and the little green padlock! In addition, Let's Encrypt launched in 2018 the support for wildcard certificates: it is now possible to request a certificate for "*.domain.com" rather than "pouet.domain.com, pouet2.domain. com, ... ". Last, you can also add authentication for interfaces that doesn't natively provide it.
I set up this configuration because I have an Asus router - AC86U - behind the box provided by my ISP, it is there to fill the gaps of this box: custom DNS, firewall and advanced DHCP, VPN server and client, dnsmasq, etc. And this router also allows me to run nginx - which I use as a reverse proxy - and to use my Ovh domain with my dynamic IP address (DynHost).
I originally did this markdown file to remember what I had done. So why not share?
The Merlin firmware is a modification of the official Asus firmware. It has the advantage of offering many improvements without removing Asus pleasant graphical interface. It also allows Entware to be used - I'll come back to this a little later.
Installing Merlin is very simple, just download the firmware from https://www.asuswrt-merlin.net/download, and flash the file from Administration > Firmware Upgrade.
There is no real risk in using Merlin, as it is very easy to go back, and reinstall the official firmware.
Once the router is running Merlin, go to Administration > System, and activate the JFFS partition.
Still on the same page, enable SSH access by selecting "LAN Only", and enable HTTPS access to the interface. The port will switch from 80 to 8443 automatically:
JFFS is a writeable partition of the router's flash memory, which will allow you to store small files (such as scripts) without the need to have an USB disk connected. This partition will survive a reboot. It will also be available quite early on boot (before USB disks). In short, this partition is necessary for what we want to do.
The router's graphical interface, reached with address 192.168.1.1, uses port 80 by default. Except that our reverse proxy will need ports 80 and 443, so we move the GUI to port 8443. The router will be accessible via https://192.168.1.1:8443, freeing ports 80 and 443.
As for SSH access, it will be necessary later, because most of the tutorial will use a terminal and command lines. I personally use PuTTY with Windows.
Entware is free software, it is a packet manager for embedded systems, like Nas or routers. It allows adding a lot of programs normally unavailable, like the nano text editor for example. Entware's advantage in this tutorial is that it allows you to install nginx.
Entware requires an EXT2 formatted USB flash drive, connected to the router's USB port. Easy with Linux, less with Windows... The best is to use MiniTool Partition Wizard Home Edition if your PC is running Windows. Nothing complex: install the application, right click on the USB key, and delete the partition or partitions already present. Right-click and create an EXT2 partition of at least 2GB. Click ok, and apply.
The key plugged in, we connect in SSH to the router with PuTTY, and type:
entware-setup.sh
The terminal will show:
Info: This script will guide you through the Entware installation.
Info: Script modifies only "entware" folder on the chosen drive,
Info: no other data will be touched. Existing installation will be
Info: replaced with this one. Also some start scripts will be installed,
Info: the old ones will be saved to .entwarejffs_scripts_backup.tgz
Info: Looking for available partitions...
[1] --> /tmp/mnt/sda1
=> Please enter partition number or 0 to exit
We choose the partition by typing the corresponding digit, and hop. It's over.
Note: if your router allows it, another message will appear before asking for a partition, asking for the version of entware you want to use, 32 or 64bits:
Info: This platform supports both 64bit and 32bit Entware installations.
Info: 64bit support is recommended, but 32bit support may be required
Info: if you are using other 32bit applications.
Info: The 64bit installation is also better optimized for newer kernels.
=> Do you wish to install the 64bit version? (y/n)
If that's the case, answer yes.
The key plugged in, we connect in SSH to the router with PuTTY, and type:
amtm
The terminal will launch amtm script. Just type "i" to launch the install menu, then type "ep" to install entware.
As indicated in the introduction, I have an Ovh domain name, and I want to access the different services I host at home, via this address. Problem, I don't have a static ip: if I link pouet.fr to my ip address, at the first ip change, the address will no longer point to my home. So I will create records at Ovh and use my router to update the linked ip address. To do this, you have to do a manipulation on Ovh admin console, and create a script on the router that will run periodically to update the IP address.
In the Ovh admin console, go to the domain you want to use, and click on DynHost 1️⃣ , then on manage accesses 2️⃣. In the window that opens, you create an access 3️⃣
- The suffix will be the identifier that we will use in the script: put what you want.
- The subdomain is used to indicate the extent to which the ip address will be updated.
- And finally, a password of your choice that will be used for the script.
Back in the Dynhost window, we click on 'add a Dynhost' 4️⃣ and we add current public ip (found on http://myip.dnsomatic.com/ for example). For the subdomain, I put nothing, but there is no obligation to do like me.
Finally, last step, we will create as many redirections as there are services you want to access. For that, we go in redirection, and we create a CNAME redirection to the dynhost domain:
It is also possible to create a wildcard redirect. Just delete the existing CNAME redirections if there are any, and then add a CNAME entry in the DNS zone from *.pouet.fr to pouet.fr
In order for the router to update the ip address to which the domain points, you must use the router's DDNS function. By default, a series of suppliers like no-ip is proposed, but not Ovh. So you have to create a personal script. You are lucky, I tested and adapted one.
We connect to the router via the terminal, and:
wget https://github.com/pedrom34/TutoAsus/raw/master/Data/asuswrt-ovh-ddns-start.sh -O /jffs/scripts/ddns-start
Then we edit the downloaded script.
vi /jffs/scripts/ddns-start
We update the identification information of the DynHost Ovh (user & password) that we created in step 4.1., as well as the domain (pouet.fr). In vi, simply type "i" to insert text at the cursor position.
U=user
P=password
H=domain
To exit vi and save the script, press Esc, and type "ZZ" without quotes and in capital letters. We make the script executable:
chmod a+x /jffs/scripts/ddns-start
And we return in router interface, in WAN and DDNS, we activate DynHost custom:
Then we apply and restart.
Tada! We have a domain name that points to the ip of our router! Even if your IP address changes!
Note that the ddns-start script considers by default that the router is, like mine, double Nated behind an ISP box. If this is not the case, adapt the script by adding "#" before "IP=$(wget..." to line 29 of the script.
Now that everything is set, we can install nginx.
opkg install nginx-ssl
Why nginx-ssl instead of nginx? Because nginx doesn't include some interesting modules for https security.
We add rules in the firewall so that nginx can listen to ports 80 and 443:
vi /jffs/scripts/firewall-start
Copy-paste those rules in the script (and as before, to exit vi, press Esc, then "ZZ"):
#!/bin/sh
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -p tcp --dport 443 -j ACCEPT
In my case, the services-start script that entware creates, won't start nginx automatically. This is due to the fact that the delay is not sufficient to allow the usb key to be mounted. So I changed it from this:
#!/bin/sh
RC='/opt/etc/init.d/rc.unslung'
i=30
until [ -x "$RC" ] ; do
i=$(($i-1))
if [ "$i" -lt 1 ] ; then
logger "Could not start Entware"
exit
fi
sleep 1
done
$RC start
To that:
#!/bin/sh
RC='/opt/etc/init.d/rc.unslung'
i=60
until [ -x "$RC" ] ; do
i=$(($i-1))
if [ "$i" -lt 1 ] ; then
logger "Could not start Entware"
exit
fi
sleep 1
done
$RC start
Then, you have to make the scripts executable:
chmod a+x /jffs/scripts/*
Without doubt the trickiest part because the configuration of nginx depends very much on the services you want to access... In any case, it is necessary to modify the current configuration in "/opt/etc/nginx/nginx.conf". So, with vi:
vi /opt/etc/nginx/nginx.conf
And exit vi as explained above to save.
There are lots of other configurations on the internet. You can take a look at linuxserver.io reverse-proxy confs here. A short example with https only:
user nobody;
worker_processes 1;
#error_log /opt/var/log/nginx/error.log;
#error_log /opt/var/log/nginx/error.log notice;
#error_log /opt/var/log/nginx/error.log info;
#pid /opt/var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
server_names_hash_bucket_size 64;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log /opt/var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
## Compression
gzip on;
gzip_buffers 16 8k;
gzip_comp_level 9;
gzip_http_version 1.1;
gzip_min_length 10;
gzip_types text/plain text/css application/x-javascript text/xml;
gzip_vary on;
gzip_static on; #Needs compilation with gzip_static support
gzip_proxied any;
gzip_disable "MSIE [1-6]\.";
server {
listen 80;
server_name localhost;
location / {
root /opt/share/nginx/html;
index index.html index.htm;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 80;
server_name *.domain.tld;
return 301 https://$host$request_uri;
}
server {
listen 443;
server_name localhost;
ssl on;
ssl_certificate cert.crt;
ssl_certificate_key cert.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Xss-Protection "1";
add_header Content-Security-Policy "default-src 'self'";
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate cert.crt;
resolver 1.1.1.1 valid=60s;
resolver_timeout 2s;
location / {
root html;
index index.html index.htm;
}
location /robots.txt {
return 200 "User-agent: *\nDisallow: /";
}
}
include /opt/etc/nginx/sites-enabled/*.conf;
}
For the resolver, near the end of the file, you must specify a DNS resolver. I specified here the cloudflare DNS, but it is possible to use the router's local ip address.
Once the modification is done, create a .conf file per service you want to proxify in /opt/etc/nginx/sites-enabled/. For instance:
vi /opt/etc/nginx/sites-enabled/kodi.domain.tld.conf
and:
server {
listen 443;
server_name kodi.domain.tld;
ssl on;
ssl_certificate cert.crt;
ssl_certificate_key cert.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Xss-Protection "1";
add_header Content-Security-Policy "default-src 'self'";
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate cert.crt;
resolver 1.1.1.1 valid=60s;
resolver_timeout 2s;
location / {
proxy_pass http://192.168.0.10:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
}
location /robots.txt {
return 200 "User-agent: *\nDisallow: /";
}
}
There are many ways to get a free Let's Encrypt certificate. For routers, I find that the most suitable method is to use the acme.sh script.
This script is amazing: it adapts to a lot of situations thanks to its many options, and is very light!
On the following part, I use the script with my domain name, via the Ovh API. If you are not in this situation, refer to the acme.sh wiki.
So, we start with downloading the script.
wget https://github.com/Neilpang/acme.sh/archive/master.zip
Unzip the archive. I chose to unzip it to /opt, that is the usb device plugged in. In any cases, the folder will be deleted afterwards.
unzip master.zip -d /opt
Go to the folder
cd /opt/acme.sh-master/
Make the script executable.
chmod a+x /opt/acme.sh-master/*
And then install the script to /opt/scripts/acme.sh, the "--home" argument allows you to define the installation folder.
./acme.sh --install --home "/opt/scripts/acme.sh"
I configure the script to use Ovh API to create TXT fields in domain records, thus justifying my property for Let's Encrypt.
The keys are created on https://eu.api.ovh.com/createApp/
Make a note of the information displayed, then, in the terminal, go to the acme.sh folder
cd /opt/scripts/acme.sh
And install the Ovh API keys that we got in the previous step, by typing in the terminal (replace by your information):
export OVH_AK="Ovh Application Key"
export OVH_AS="Ovh Application Secret"
Then, generate the certificate. Here, we can see that I request a wildcard certificate *.domain.tld* as well as for the root domain (domain.tld). I prefer letsencrypt but the default server is zerossl, see here for more information on why I use "--server" argument.
./acme.sh --server letsencrypt --issue -d *.domain.tld -d domain.tld --dns dns_ovh
Anyway, it will fail, and return an error message like this:
Using Ovh endpoint: ovh-eu
Ovh consumer key is empty, Let's get one:
Please open this link to do authentication: https://eu.api.ovh.com/auth/?credentialToken=n0Qbjm6wBdBr2KiSqIuYSEnixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Here is a guide for you: https://github.com/Neilpang/acme.sh/wiki/How-to-use-Ovh-domain-api
Please retry after the authentication is done.
Error add txt for domain:_acme-challenge.*.domain.tld
Indeed, you must go, the first time only, to the address indicated in the script to be able to activate the API. Select "Unlimited" for the validity period.
Then do it again, this time it will work:
./acme.sh --server letsencrypt --issue -d *.domain.tld -d domain.tld --dns dns_ovh
Install the script in nginx.
./acme.sh --home "/opt/scripts/acme.sh" --install-cert -d domain.tld \
--key-file /opt/etc/nginx/cert.key \
--fullchain-file /opt/etc/nginx/cert.crt \
--reloadcmd "/opt/etc/init.d/S80nginx reload"
Note that the path I indicate for the key and the certificate is the one indicated in the nginx configuration. Make sure it's the same!
We need to add a line to services-start for the automatic renewal of certificates, which will be launched every day at 2am. To do so, we need to type vi /jffs/scripts/services-start and then add this line (again, type i, then Esc and ZZ once you pasted the line):
cru a "acme.sh" '0 2 * * * /opt/scripts/acme.sh/acme.sh --cron > /dev/null'
The automatic update of acme.sh is activated via the following command line:
./acme.sh --upgrade --auto-upgrade
The acme.sh-master folder in opt can now be deleted.
rm -r /opt/acme.sh-master/
And finally you can start nginx:
/opt/etc/init.d/S80nginx start
Note: at this point, nginx might not have enough memory. This error will show:
nginx: [alert] mmap(MAP_ANON|MAP_SHARED, 52428800) failed (12: Cannot allocate memory)
Just reboot the router to solve the problem, then try again to launch nginx.
At home, nginx works very well, but a router update can remove all the work done here. Remember to save the router configuration and the JFFS partition from the router interface!
If nginx does not launch, try the following command to test the configuration to troubleshoot:
nginx -t
If the configuration file is modified, the configuration can be reloaded without restarting nginx by typing:
nginx -s reload
Not being a computer specialist or network administrator, if I could do all this, it's by standing on the shoulders of giants. Do not hesitate to consult these sites which helped me a lot, to adapt this modest tutorial to your environment:
- Sauvageau E. asuswrt-merlin: Enhanced version of Asus’s router firmware (Asuswrt) - Wiki [Internet]. 2018 [accessed on 19-04-2018]. Available on: https://github.com/RMerl/asuswrt-merlin/wiki
- Neilpang. acme.sh: A pure Unix shell script implementing ACME client protocol - Wiki [Internet]. 2018 [accessed on 19-04-2018]. Available on: https://github.com/Neilpang/acme.sh/wiki
- Xuplus. 搞定Merlin使用DNS实现Let’s Encrpt证书,使用SSL安全访问后台 - 梅林 - KoolShare - 源于玩家 服务玩家 [Internet]. Koolshare. 2016 [accessed on 19-04-2018]. Available on: http://koolshare.cn/thread-79146-1-1.html
- HTPC Guides [Internet]. Mike. Use Afraid Custom Dynamic DNS on Asus Routers; 17-05-2016 [accessed on 19-04-2018]. Available on: https://www.htpcguides.com/use-afraid-custom-dynamic-dns-asus-routers/
- Törnqvist G. Nginx Reverse Proxy on Asus Merlin [Internet]. Göran Törnqvist Website. 2015 [accessed on 19-04-2018]. Available on: http://goran.tornqvist.ws/nginx-reverse-proxy-on-asus-merlin/
- jeromeadmin. Firmware Asuswrt-Merlin - T[echnical] eXpertise [Internet]. T[echnical] eXpertise. 2014 [accessed on 19-04-2018]. Available on: http://tex.fr/firmware-asuswrt-merlin/
- SSL Configuration Generator [Internet]. Mozilla Foundation. Generate Mozilla Security Recommended Web Server Configuration Files; [accessed on 23-04-2018]. Available on: https://mozilla.github.io/server-side-tls/ssl-config-generator/
- 2018-09-11: use more than one DynDNS: English 🇬🇧, Français 🇫🇷
- 2018-09-18: setup nginx with symlinks: English 🇬🇧, Français 🇫🇷
- 2018-12-05: use logrotate to handle nginx logs: English 🇬🇧, Français 🇫🇷