Tag Archives: nginx

HTTPS: how to add TLS ciphers on nginx (update regularly)

HTTPS is a great improvement to a website security. However, HTTPS comes in different flavours and among these there are very weak ones.

Among protocols, SSL have to be avoided because it is not secure. Its successor, TLS, comes in different versions and supports different ciphers. To be short, the cipher is the encryption method/algorihms the website and the client use to talk each other.

The combination of protocols and ciphers available to implement HTTPS will limits the type of clients capable to access the website.

To be sure your website will not lose traffic, you have to balance the strongest ciphers available with the most compatible but still secure, dropping all weaker ciphers.

Check the strenght of your HTTPS implementation

If you’ve already implemented HTTPS on your website, first you’ve to check ist current security status of protocols and ciphers.

Check your hostname on Qualys SSL Labs pasting the HTTPS protected domain on the Test your server section. It’s a fast method with a very detailed output for public websites.

The report will give your hostname a rank, a detailed list of issues, browser support, and the complete list of supported ciphers. Among these ciphers, you can get some ciphers highlighted in yellow. You have to get the rid of these no matter what.

The list of ciphers actually differs from a typical cipher declaration on nginx because nginx can use the OpenSSL naming and Qualys uses IANA naming.

Here’s an helpful conversion table by Mozilla where you can convert IANA to OpenSSL and the other way round. Take note of the weak ciphers but wait before start to cut your cipher declaration on nginx.

You’ve to check how many visitors you’ll lose after the cut first.

Get the website statistics

Using Google Analytics or similar services and software, go to the Audience > Technology > Browser to get a list of your visitors’ browsers. Select a timespan like the last year or less.

You can add Browser version or OS version as secondary dimension to match the list of supported browsers from SSL Labs. You’ll get something similar:

analytics-technology-browsers

Well, someone is still using Internet Explorer 9.0 in 2018.

Since Internet Explorer running on old Windows versions (like XP) is one of the most troublesome combination, check how many visitors use this legacy software.

On Google Analytics type on the search box “Internet Explorer” and you’ll get the browser usage of this legacy browser. Select OS version as secondary dimension to get a list of OSes using IE.

Compare this list with the report from SSL Labs and with the conversion table from Mozilla cited above and count the number of visitors you want to cut off from your website in the sake of security.

Cut the weak ciphers

Trimming down the ciphers declaration on nginx conf you’ll get something like this:

ssl_ciphers ‘ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!3DES’;

Each cipher is separated by a ‘:’ and at the end some elements (typically using OpenSSL naming) are forbidden with a ‘!’.

Here’s the context:

server {
        # the port your site will be served on
        listen      443 ssl;
        # the domain name it will serve for
        # substitute your machine's IP address or FQDN
        server_name example.com www.example.com;
        ssl_certificate /path/to/fullchain.pem;
        ssl_certificate_key /path/to/privkey.pem;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;
        # cfr. ........................................
        ssl_ciphers ** PASTE CIPHERS HERE **;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_stapling on;
        ssl_stapling_verify on;
        add_header Strict-Transport-Security max-age=15768000;
        charset     utf-8;
        # This is for Let's Encrypt
        location ^~ /.well-known {
                alias /path/to/.well-known;
                allow all;
        }

        # max upload size
        client_max_body_size 75M;   # adjust to taste

        location /webpath  {
                alias /path/to/web;
        }
}

Change the conf file, reload nginx (on CentOS 7 systemctl reload nginx) and then re-run the SSL Labs test.

The Qualys’ tool will show you the new incompatibility with legacy browsers in the Handshake Simulation section:

tls-handshake

Modern protocols and ciphers implemented using the above declaration on nginx cut off IE 8 on XP and IE 6, the report explain.

According to the technology used by visitors of the analyzed website, few visits are sacrificed for better security for both visitors and host.

Tune these settings according to your needs, keep monitoring the tecnology used by site visitors and dropping legacy system progressively, with Modern compatibility as a (not so) long term objective.

Advertisements
HTTPS, encrypt via SSL / TLS

Free SSL certificates and how to install on nginx in 10 steps

Here how you can get free SSL cerificates using Let’s Encrypt. Forget about the expire of certificates using the auto-renewal script. A complete reference to install a Let’s Encrypt certificate is this Digital Ocean’s howto. Here there’s a quick guide based on it, plus some additional suggestions. Here we go!

The following code download the script and make it executable. (1)

cd /usr/local/sbin
wget https://dl.eff.org/certbot-auto
chmod a+x /usr/local/sbin/certbot-auto

The following code create a path for ssl certificate. Change /usr/local/etc/my/files/path/ssl_cert with a path for where you’ll store certificates, you can select a path not in your document root. (2)

mkdir /usr/local/etc/my/files/path/ssl_cert

Now edit your /etc/nginx/conf.d/mysites.conf and add this into the server {…} directive to make available example.com/.well-known url (3):

server {
listen 80;
server_name example.com www.example.com mysite.com www.mysite.com;
        location ^~ /.well-known {
                alias /usr/local/etc/my/files/path/ssl_cert/.well-known;
                allow all;
        }
}

At this time you’ve to make available .

Check syntax and reload nginx:

nginx -t
systemctl reload nginx

Now execute the script to install certificates for your domains. Remember to use the command with -d domain-without-www -d www-domain in this order. (4)

  1. Install all needed dependencies for your system (via yum on RedHat based distro and apt on Debian based)
  2. Generate a valid certificate
certbot-auto certonly -a webroot --webroot-path=/usr/local/etc/my/files/path/ssl_cert -d example.com -d www.example.com -d mysite.com -d www.mysite.com

An auto check will be performed and you will get a Congratulation message.

Now generate a strong Diffie-Hellman group with this command (5):

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Check syntax and if ok reload the nginx server to apply changes and . (6)

nginx -t
systemctl reload nginx

Auto-renewal

A certificate will be valid for a short period of time, e.g. 3 months.

To auto-renew the certificate for all of your domains, you should add the auto-renewal command to cron.

You can read how to renew certificates on cron here.

Enable SSL on nginx

To enable SSL on nginx, if you have already a mysite.conf file mapped for uncrypted connection on port 80. Inside the /etc/nginx/conf.d directory, copy the file as mysite_ssl.conf and:

Change all occurrences of:

listen 80;

to:

listen 443 ssl;

In this way nginx will listen to 443 port on SSL. Ensure you have this port available externally (firewall and/or Selinux audit2allow). (8)

In the original file, mysite.conf, you can delete all entries but you have to keep the well-know part (step 3). This will avoid errors by Let’s Encrypt script.

Add and enable cyphers. Here there’s a good cyphers list, reliable for compatibile but secure using TLS only. (9)

server {
    # the port your site will be served on
    listen      443 ssl;
    # the domain name it will serve for
    server_name example.com; # substitute your machine's IP address or FQDN
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
    ##### Cyphers and SSL fine tuning #####
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
    ##### END Cyphers and SSL fine tuning #####
    # charset     utf-8; etc...
}

Test nginx syntax with:

nginx -t

and then reload nginx to apply changes (10), on CentOS:

systemctl restart nginx

How to enable gzip on proxy servers on nginx

I use often Gunicorn as web server for django applications.

Usually I use Apache but I’m starting to use nginx as webserver to serve both the static files and the proxied gunicorn response.

I need to do something like what I’ve done with Apache to compress the response after I received from django since I’ve noticed that in my case compressing it before using @gzip_page decorator is more detrimental to performance than doing it after.

Here an essential mysite.conf to put in /etc/nginx/conf.d.

server {
    listen      80;
    server_name .example.com other.domain.example.com;
    charset     utf-8;
    # max upload size
    client_max_body_size 75M;
    # Enable gzip for proxied requests and static files
    gzip on;
    gzip_proxied any;
    gzip_vary on;
    gzip_http_version 1.1;
    gzip_types application/javascript application/json text/css text/xml;
    gzip_comp_level 4;
    # Serve static files via nginx
    location /media  {
        alias /usr/local/etc/files/mysite/media_root;
    }
    location /static {
        alias /usr/local/etc/files/mysite/static_root;
    }
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        # Serve dynamic requests via gunicorn on custom port (e.g. 8585)
        # and gzip the response
        if (!-f $request_filename) {
            proxy_pass http://localhost:8585;
            break;
        }
    }
}

In this way, content by Gunicorn is served to nginx and before to send it to client nginx gzip it, here with a compression level of 4 of 9. A compression between 1 and 4 is generally acceptable for any text content, avoiding to stress the CPU too much for a small compression gain.

Even the static files like css and javascript are served compressed for client with HTTP 1.1 support. You can add more type to compress including the mime type on the gzip_types row.

Read also on the same topic: How to enable gzip on proxy servers on Apache

Reference: