nginx Reverse Proxy & Ubuntu 20.04 LTS
Test scenario #
- a single root or virtual server with a public IP address
nginxis installed and upstreamed on this server as a reverse proxy- the proxy manages all (sub)domains, for example
sub.testdomain.com - if a visitor calls
sub.testdomain.com, the proxy forwards the request on the corresponding server or service and port - this server may, for example, host multiple Docker instances (i.e. multiple web servers) on different ports
- the Docker instances could also run on the reverse proxy itself
Here, we want HTTP requests to be automatically redirected to HTTPS (via the respective (sub)domain). In this example, nginx runs on a separate host and redirects requests to an IP address (i.e. separate server) from the internal network.
Requirements #
- installed Ubuntu 20.04 LTS server image
- in general the instructions can be adapted to Debian, for Fedora, openSUSE etc. (the package name and manager have to be adapted)
- secured (activated firewall, fail2ban, SSH config etc.)
- domain, which points to the server via DNS entry (or an dynamic DNS provider)
install nginx #
sudo apt install nginxCreate a common index.html file #
This path will be used for all HTTP requests and is basically used for initial configuration of (sub)domains and certificate creation.
sudo mkdir /var/www/testdomain.comsudo nano /var/www/testdomain.com/index.html<!DOCTYPE html>
<html>
<body>
Hello World!
</body>
</html>Create and customize configuration file #
sudo nano /etc/nginx/sites-available/testdomain.com.confserver {
listen 80 default_server; # IPv4
listen [::]:80 default_server ipv6only=on; # IPv6 (comment out if no need)
server_name testdomain.com www.testdomain.com;
root /var/www/testdomain.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Clear default config #
sudo systemctl stop nginxsudo rm /etc/nginx/sites-enabled/defaultCreate symlink for the configuration file #
sudo ln -s /etc/nginx/sites-available/testdomain.com.conf /etc/nginx/sites-enabled/testdomain.com.confTest configuration #
sudo nginx -tsudo systemctl start nginxsudo systemctl status nginxSet firewall rule #
The prerequisite is that the ufw firewall is enabled.
Briefly check if nginx is present in the firewall profiles:
sudo ufw app listThen set the following rule for nginx:
sudo ufw allow 'Nginx Full'Install certboot (as of August 2020) #
sudo apt-get install certbot python3-certbot-nginxInstall certboot (as of May 2020, now obsolete) #
If you install certbot from the official repositories, you will get the following error during certificate creation:
AttributeError: module 'acme.challenges' has no attribute 'TLSSNI01'.Until this is fixed, there is a corresponding script via Github:
curl -o- https://raw.githubusercontent.com/vinyll/certbot-install/master/install.sh | bashCertbot can be uninstalled via the following script, but this is not intended here and is just a side note.
curl -o- https://raw.githubusercontent.com/vinyll/certbot-install/master/uninstall.sh | bashCreate Let’s Encrypt certificate #
sudo certbot --rsa-key-size 4096 --nginxSelect the desired domains, enter your email address and confirm the request to automatically set up a redirect from HTTP to HTTPS. The site would now be accessible via www.testdomain.com or testdomain.com and equipped with a Let’s Encrypt certificate.
A+ SSL rating #
Using SSL Labs we would get an “A” for the server rating up to this point. However, we want the maximum possible “A+” rating. For additional hardening, a Diffie-Hellmann group should be created via the following command:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096The setup took about 30 minutes on my VM with one CPU core assigned - so have some patience at this point and maybe go get a coffee.
Next, create a general SSL configuration file for all future sites, this is where the previously generated DH group is entered:
sudo nano /etc/nginx/snippets/ssl.confWith the configuration given below, we achieve a cipher strength of 90%. To get to 100%, the corresponding part should be commented out resp. commented again, but older devices are not able to handle the corresponding cipher types and refuse the connection. 90% is enough for an A+ rating on SSL Labs (and secure).
The following content is added here:
# Hide the nginx version
server_tokens off;
# Disable gzip due the HTTPS BREACH attack. Only activate if it is really required by your application.
gzip off;
# SSL Settings
# The generated DH Group
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
# Ciphersuites recommendation from the chiper.li
# Use this chipersuites to get 100 points of the SSLabs test
# Some device will not support
# ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384";
# Mozilla Ciphersuits Recommendation
# Use this for all devices supports
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_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OSCP stapling
ssl_stapling on;
ssl_stapling_verify on;In addition, we create another configuration file:
sudo nano /etc/nginx/snippets/header.conf And maintain below specified content for further hardening of the proxy:
# HSTS Header, only use HTTPS, applied on all subdomains
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
add_header X-Robots-Tag none;
# Disable mime content-type sniffing
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Content-Type-Options "nosniff" always;
# Deny your page to be embedded in frames / iframes to protect from clickjacking
add_header X-Frame-Options DENY;
# Enable Cross-Site scripting protection
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Feature-Policy "geolocation 'self'";Now the configuration file of the domain should be adjusted again - the SSL configuration created earlier must be included.
sudo nano /etc/nginx/sites-available/testdomain.com.confThe SSL entries automatically generated by certbot are deleted and replaced with the previously created SSL configuration. In addition, the parameter for OSCP Stapling is added.
server {
server_name testdomain.com www.testdomain.com;
root /var/www/testdomain.com;
index index.html;
location / {
include /etc/nginx/snippets/header.conf;
try_files $uri $uri/ =404;
}
listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/testdomain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/testdomain.com/privkey.pem; # managed by Certbot
ssl_trusted_certificate /etc/letsencrypt/live/testdomain.com/chain.pem;
include /etc/nginx/snippets/header.conf;
include /etc/nginx/snippets/ssl.conf;
}
server {
if ($host = www.testdomain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = testdomain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server; # IPv4
listen [::]:80 default_server ipv6only=on; # IPv6 (comment out if there is no need)
server_name testdomain.com www.testdomain.com;
return 404; # managed by Certbot
}Finally, the configuration should be tested again and nginx restarted so that the configurations will be applied:
sudo nginx -tsudo systemctl restart nginxAn SSL test should now show you an A+ rating.
For each additional site or subdomain, the further process is as follows: #
- create the configuration file of the new (sub)domain:
sudo nano /etc/nginx/sites-available/sub.testdomain.com.confserver {
listen 80; # IPv4 only
server_name sub.testdomain.com;
root /var/www/testdomain.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}- create symlink in
sites-enabledpath:
sudo ln -s /etc/nginx/sites-available/sub.testdomain.com.conf /etc/nginx/sites-enabled/sub.testdomain.com.conf- test the configuration and restart nginx
sudo nginx -tsudo systemctl restart nginx- create Let’s Encrypt certificate and select the new domain during the query:
sudo certbot --rsa-key-size 4096 --nginx- Edit the configuration file for the reverse proxy once again, entering the correct IP address and port, entering our general SSL configuration file and setting the stapling parameter.
sudo nano /etc/nginx/sites-available/sub.testdomain.com.confPay attention to the correct or changed paths, the configuration file is not the same as above, but can basically be taken over for all further pages:
testdomain.comsub.testdomain.comIP-ADDRESS:PORT
server {
server_name sub.testdomain.com;
root /var/www/testdomain.com;
index index.html;
location / {
proxy_pass http://IP-ADRESSE:PORT/;
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;
# additional settings, optional
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
client_max_body_size 0;
}
listen [::]:443 ssl http2; # managed by Certbot
listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/sub.testdomain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/sub.testdomain.com/privkey.pem; # managed by Certbot
ssl_trusted_certificate /etc/letsencrypt/live/sub.testdomain.com/chain.pem;
# the previously created configuration files will be included
include /etc/nginx/snippets/ssl.conf;
include /etc/nginx/snippets/header.conf;
}
server {
if ($host = sub.testdomain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80; # IPv4 only
server_name sub.testdomain.com;
return 404; # managed by Certbot
}- last test of the configuration and restart of nginx
sudo nginx -tsudo systemctl restart nginxDone!