Quick Links

If you just need encryption for internal server connections or non-user facing sites, signing your own SSL certificates is an easy way to avoid dealing with an external certificate authority. Here's how to set it up in nginx.

If you are more interested in getting free SSL certificates, you can always use LetsEncrypt, which is more suitable for public servers with user-facing websites because it will show up as coming from a recognized certificate authority in user's browsers. However, it can't be used to encrypt private IP addresses, which is why you must sign a cert yourself.

Generate and Self-Sign an SSL Certificate

To do this, we'll use the openssl utility. You likely have this installed already, as it's a dependency of Nginx. But if it's somehow missing, you can install it from your distro's package manager. For Debian-based systems like Ubuntu, that would be:

sudo apt-get install openssl

After openssl is installed, you can generate the certificate with the following command:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx.key -out /etc/ssl/certs/nginx.crt

You'll be asked for some info about your organization. Because this is self-signed, the only one that really matters is "Common Name," which should be set to your domain name or your server's IP address.

Country Name (2 letter code) []:

State or Province Name (full name) []:

Locality Name (eg, city) []:

Organization Name (eg, company) []:

Organizational Unit Name (eg, section) []:

Common Name (eg, fully qualified host name) []: your_ip_address

Email Address []:

This will take a second to generate a new RSA private key, used to sign the certificate, and store it in /etc/ssl/private/nginx.key. The certificate itself is stored in /etc/ssl/certs/nginx.crt, and is valid for an entire year.

We'll also want to generate a Diffie-Hellman group. This is used for perfect forward secrecy, which generates ephemeral session keys to ensure that past communications cannot be decrypted if the session key is compromised. This isn't entirely necessary for internal communications, but if you want to be as secure as possible you shouldn't skip this step.

sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

This does take a while---about an hour depending on how fast your server is. Grab some lunch, and come back to your terminal in a bit to configure Nginx.

Configure Nginx to Use Your Private Key and SSL Certificate

To make things easy, we'll put all the configuration in a snippet file that we can include in our nginx server blocks. Create a new configuration snippet in nginx's snippets directory:

touch /etc/nginx/snippets/self-signed.conf

Open it up in your favorite text editor, and paste the following in:

ssl_certificate /etc/ssl/certs/nginx.crt;

ssl_certificate_key /etc/ssl/private/nginx.key;

ssl_protocols TLSv1.2;

ssl_prefer_server_ciphers on;

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;

ssl_session_timeout 10m;

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 X-Frame-Options DENY;

add_header X-Content-Type-Options nosniff;

add_header X-XSS-Protection "1; mode=block";

ssl_dhparam /etc/nginx/dhparam.pem;

ssl_ecdh_curve secp384r1;

The first two lines of this snippet configure nginx to use our self-made certificate and our own private key. The next block is general SSL settings, and finally the last two lines configure nginx to use our Diffie-Hellman group for forward security. You can omit this if you didn't feel like waiting.

The only other thing to enable would be HTTP Strict Transport Security, which configures your site to always use SSL. This would require a permanent redirect from HTTP to HTTPS, so you should verify that SSL works before enabling it.

Now, modify your primary nginx configuration (usually located at /etc/nginx/nginx.conf for single sites, or under your domain name in /etc/nginx/sites-available for multi-site servers), and source the snippet:

server {

listen 443 ssl;

listen [::]:443 ssl;

include snippets/self-signed.conf;

server_name example.com www.example.com;

. . .

}

You'll also want to set up a redirect from HTTP to HTTPS, which you can do with an additional server block listening on port 80:

server {

listen 80;

listen [::]:80;

server_name example.com www.example.com;

return 302 https://$server_name$request_uri;

}

This is a 302 redirect, which is only temporary. You'll want to switch this to 301 if everything works properly.

Test your configuration by restarting nginx:

sudo service nginx restart

Because HTTPS traffic uses port 443, you'll need to configure your firewalls to allow transport over that port. If you're using iptables or UFW, you'll need to open ports from the command line. If you're using a hosting service like AWS that has a built in firewall, you'll need to also open them from their web interface.

AWS firewall interface

If your service is operating entirely within your LAN, you might want to whitelist your specific subnet of IP addresses to disable access from outside the LAN, and access your servers through a VPN connection.

If everything works correctly, you should now be able to access your server over HTTPS. Your web browser may display a warning like this:

chrome SSL warning

Don't worry, this is expected, and the reason why you can't use these certificates for client-facing websites. You'll have to manually confirm that you trust the server in order to access it.

The warning displayed here is slightly misleading---your site is secure so long as the private key is not compromised, and it's perfectly secure if you set up Diffie-Hellman forward secrecy. The problem lies in identity, as Chrome can't verify that your server is who it says it is, because you signed the cert yourself.

Once you've verified that there are no issues with SSL, you can switch the HTTP redirect to a 301 redirect:

return 301 https://$server_name$request_uri;

And restart nginx to apply the changes.