Using Let's Encrypt for NGINX on CentOS 7

Recently, I set up several web apps on my VPS. As these apps requires user credentials to access, I have to make sure they are secure. Therefore, I decided to enable HTTPS on these sites. Also, since Chrome shows HTTP sites as “Not secure” starting from Chrome 56, it’s a good idea to enable HTTPS support on every website you are running. In this post, I will describe how to enable HTTPS support in NGINX with SSL/TLS certificate from Let’s Encrypt on CentOS 7.

Installation of NGINX and Certbot

First, let’s make sure NGINX and Certbot are installed. Certbot is an command line utility used to obtain certificates from Let’s Encrypt, or any other certificate authority (CA) that supports the Automatic Certificate Management Environment (ACME) protocol. Both of these software are provided in the Extra Packages for Enterprise Linux (EPEL) package repo.

$ sudo yum install -y epel-release
$ sudo yum install -y nginx certbot

Start NGINX and access your server with either its IP or a domain name (with A record pointing to that IP). You should be able to see an NGINX test page.

$ sudo systemctl start nginx
$ sudo systemctl enable nginx # start nginx on boot

Configuring NGINX for obtaining certificates

When you install NGINX on CentOS 7, there is a default server block in /etc/nginx/nginx.conf. This block will handle all requests not matched by other server blocks you define in /etc/nginx/conf.d. And you can extend this default server block by adding definitions in /etc/nginx/default.d. Let’s add the file /etc/nginx/default.d/well-known.conf

location ~ /.well-known {
	allow all;
}

This block is required to verify that you own a domain before any certificate could be issued to you (using Certbot’s webroot plugin). By placing it under /etc/nginx/default.d, you can make sure that any domain that points to this server can be verified. If you prefer not to use the default server block, you could add your own server block in /etc/nginx/conf.d and make sure the above block is inside your own definition.

Now we need to restart NGINX to reload your configuration.

$ sudo nginx -t # checks the syntax of your configuration
$ sudo systemctl restart nginx

Obtaining a certificate with Certbot

With NGINX properly configured, we could finally obtain our certificate! We will obtain the certificate using the webroot plugin of Certbot, therefore you need to check where your webroot is for a particular server block. If you use the default server block, your webroot should be /usr/share/nginx/html (you can always check /etc/nginx/nginx.conf). Also, make sure your domain’s A record points to your server (check that by accessing that domain in your browser, you should see the test page). To obtain a certificate on domain example.com and www.example.com, you could use the following command.

$ sudo certbot certonly -a webroot --webroot-path=/usr/share/nginx/html -d example.com -d www.example.com

Notice that one certificate can work with multiple domain names. However, Let’s Encrypt doesn’t support wild card domain names, so you have to write out different domain names one by one.

If you run this for the first time, it will ask your email and ask you to agree with their agreement. If everything is configured properly, you should see some output indicating that you have successfully obtained the certificate and it’s stored in /etc/letsencrypt/live/example.com. The certificate is valid for 90 days.

Enabling HTTPS in NGINX

Now we will enable HTTPS in NGINX for a domain. But first, let’s do more to further improve the security of your server. Generate a 2048-bit Diffie-Hellman Group with

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

You may have to wait for a while for it to finish. The file will be at /etc/ssl/certs/dhparam.pem once its generated.

Suppose you want to enable HTTPS for the domain example.com and www.example.com, add the following server blocks in /etc/nginx/conf.d

server {
        listen 443 http2 ssl;

        server_name example.com www.example.com;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        # From https://cipherli.st/
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve secp384r1;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout 5s;
        add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

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

        location ~ /.well-known {
                allow all;
        }

        # The rest of your server block
        # .......
}

server {
        server_name example.com www.example.com;

        return 301 https://$host$request_uri;
}

The first server block tells NGINX that you will be listening on port 443 for HTTPS requests (either with SSL or HTTP/2) on example.com and www.example.com. The middle section is from https://cipherli.st for improving NGINX security. You have to add the well-known section for re-obtaining certificate later (requests to your domain will no longer match the well-known section in the default server block).

The second server block simply redirects any HTTP request to HTTPS request. This makes sure that no one can access your website without HTTPS.

Now let’s restart NGINX.

$ sudo nginx -t
$ sudo systemctl restart nginx

If you can now access your website with HTTPS, congratulations! You just successfully enabled HTTPS on your website.

Automatic certificate renewal

As mentioned above, the certificates from Let’s Encrypt are only valid for 90 days. Therefore, we need to periodically renew our certificates. This could be done by a cron job. Add new cron jobs with

$ sudo crontab -e

And put the following content inside

15 4 * * 1 /usr/bin/certbot renew >> /var/log/renew-certs.log
18 4 * * 1 /usr/bin/systemctl reload nginx

This tells cron to attempt to renew your certificates every Monday morning at 4:15 AM and reload NGINX 3 minutes later. Certbot will check if your certificates are expiring within one month. It will only renew certificates expiring soon, so it’s OK to run this job every week. You could access the log file /var/log/renew-certs.log to check if your certificates are successfully renewed.

References

 
comments powered by Disqus