Letsencrypt: nginx reverse proxy

TLS certificates (or SSL certificates) is something lots of people might have wanted, but they rarely were willing to spend their money on it. Since a few days letsencrypt solves this problem. At least for those who run their own server. Letsencrypt comes with a different approach to certificate request and certificate require times, aiming for a more automated future.

Letsencrypt

Now that letsencrypt is in public beta everyone can benifit from it and provide a secure connection for their website users.
Because letsencrypt will only provide certificates that are valid for a maximum of 90 days, you will likely want to automate the renewal process.

Nginx

In my use case I will setting up a reverse proxy configuration as I run a dedicated server for proxying of doing TLS termination. This setup allows me to run different services to the same public ip. I run some application in Docker, some in PHP 5.6 and some tests in PHP 7.0.

The setup

Client

Letsencrypt comes with a client for doing the certificate requests. This is an application that can be installed on your server. The client fill perform some validation to verify that the domain you requests actualy resolves to the current server.
I will not be using this as they also provide a preconfigured docker image containing the client. So make sure you have your docker installation running on your reverse proxy server.

Install docker using the official instructions.

Pull the client image.

$ docker pull quay.io/letsencrypt/letsencrypt:latest

Reverse proxy

At first we need to setup the reverse proxy configuration. Nginx provides solid documentation to complete this task. But if you want to get started, here is mine:

server {
    server_name test.nousefreak.be;
    location / {
        proxy_pass http://appserver01:8092;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Make sure you have an upstream configured for appserver01 or use the ip directly.
Now it is time to reload the webserver and check if the configuration works.

Certificate request

The client provided by letsencrypt has some options to autoconfigure your webserver (apache and nginx) using theĀ letsencrypt-auto command. I will not be using this as i have application running on this proxy and do not want them to go offline during my requests. The client does have a great solution for this using the webroot plugin. This allows us to expose a folder where the client will place a temprary file to verify this server. This however requires a slight modification of our previous configuration to expose a folder on our server.

server {
    server_name test.nousefreak.be;
    location / {
        proxy_pass http://appserver01:8092;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    location '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /tmp/letsencrypt-auto;
    }
}

Now it is time to perform the certificate request. We will be mounting the temporary folder to our docker container and provide some additional arguments to indicate we want to use the webroot plugin.

sudo docker run -it --rm --name letsencrypt -v "/etc/letsencrypt:/etc/letsencrypt" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" -v /tmp/letsencrypt-auto:/tmp/letsencrypt-auto quay.io/letsencrypt/letsencrypt:latest auth -a webroot --webroot-path /tmp/letsencrypt-auto -d test.nousefreak.be

Once you complete the steps the client asks you, your certificates can be found in /etc/letsencrypt/live/.

The TLS configuration

Let's create a new configuration block for our TLS section.

server {
    listen 443 ssl spdy;
    server_name test.nousefreak.be;

    ssl_certificate     /etc/letsencrypt/live/test.nousefreak.be/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/test.nousefreak.be/privkey.pem;

    location / {
        proxy_pass http://appserver01:8092;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Now reload your webserver and verify the connection works. You can verify the quality of your connection by performing an SSL test.

Enforce HTTPS

Now that our webserver is handing TLS connections, it's time to add some forwarding rules to our non https configuration to give all users a secure connection. This will be done by redirecting all users to the HTTPS version of our proxy.

server {
    server_name test.nousefreak.be;
    location '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /tmp/letsencrypt-auto;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

By removing the proxy part and replacing it with a redirect rule, all trafic will now be redirect to the secure connection. This will not be true for the verification url as this will be used for the verification in the furure.

Renewal

Because letsencrypt only issues certificates that are valid for 90 days, you will need to automate the renewal process. Test the following command before adding it to your crontab.

sudo docker run -it --rm --name letsencrypt -v "/etc/letsencrypt:/etc/letsencrypt" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" -v /tmp/letsencrypt-auto:/tmp/letsencrypt-auto quay.io/letsencrypt/letsencrypt:latest auth -a webroot --webroot-path /tmp/letsencrypt-auto -d test.nousefreak.be --renew-by-default

It is a good idea to renew your certificate every 60 days. Run the following command and add the command.

$ sudo crontab -e -u root
0 0 */60 * * docker run -it --rm --name letsencrypt -v "/etc/letsencrypt:/etc/letsencrypt" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" -v /tmp/letsencrypt-auto:/tmp/letsencrypt-auto quay.io/letsencrypt/letsencrypt:latest auth -a webroot --webroot-path /tmp/letsencrypt-auto -d test.nousefreak.be --renew-by-default && service nginx reload

Hardening

As you might have noticed the quality of your installation will not get a high grade. This means your configuration is correct but it can be better. Here are a few changes you can make.

Protocol Support

Limit your webserver to only connect using specific protocols

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Cipher Suite

The recommended cipher suite:

ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

The recommended cipher suite for backwards compatibility (IE6/WinXP):

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-AES128-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:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

Protect your server from BEAST attack by adding this line:

ssl_prefer_server_ciphers on;

Diffie-Hellman cryptographic protocol

Generate a string EDH key. This might take some time.

$ cd /etc/ssl/certs && openssl dhparam -out dhparam.pem 2048

Add the generated key to your configuration.

ssl_dhparam /etc/ssl/certs/dhparam.pem;

HSTS - HTTP Strict Transport Security

HSTS is very important to reduce man in the middle attacks

add_header Strict-Transport-Security max-age=15768000;

Result

If you followed the steps, you should end up with something like this:

server {
    server_name test.nousefreak.be;
    location '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /tmp/letsencrypt-auto;
    }
    location / {
        add_header Strict-Transport-Security max-age=15768000;
        return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl spdy;
    server_name test.nousefreak.be;

    ssl_certificate     /etc/letsencrypt/live/test.nousefreak.be/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/test.nousefreak.be/privkey.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_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/dhparams/dhparams.pem;

    location / {
        add_header Strict-Transport-Security max-age=15768000;
        proxy_pass http://appserver01:8092;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

I hope you liked this post. Feel free to leave your feedback below.


Fork me on GitHub