LEMP Stack with Let's Encrypt Certbot

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to: navigation, search

This guide has been consolidated from a few Digital Ocean guides.

Assumptions[edit | edit source]

  • Clean install of Debian 8
  • Pointed domain name to server IP address.

Initial Setup[edit | edit source]

Edit /etc/init.d/sources.list so they look like this.

deb http://ftp.debian.org/debian/ jessie main contrib non-free
deb http://security.debian.org/ jessie/updates main contrib non-free
deb http://nginx.org/packages/debian/ jessie nginx
deb-src http://nginx.org/packages/debian/ jessie nginx

Grab the nginx signing key.

wget http://nginx.org/packages/keys/nginx_signing.key

Add the key.

cat nginx_signing.key | sudo apt-key add - 

Now we can delete the key since we've added it.

rm nginx_signing.key

Then run a package index update.

sudo apt-get update

Set your domain name. Edit /etc/hostname with any text editor.


Then edit /etc/hosts. Change existing or add the entry: www.example.com example.com example

Update changes.


Create user, disable root SSH access[edit | edit source]

Make a separate user to remotely manage since we're disabling direct outside root access. Name it something obscure, not example.

adduser example

Grant sudo privileges.

usermod -a -G sudo example

Edit SSH config.

nano /etc/ssh/sshd_config

Find Port 22 and change it to something you'll remember and that doesn't conflict with a service you're running. You'll connect to ssh with this port instead of the default, 22.

Port 35240

Find #PermitRootLogin yes and set it to the following:

PermitRootLogin no

Restart SSH service.

systemctl restart ssh

Switch to the user you made.

su - example

Install Web Stack[edit | edit source]

Install Nginx Web Server[edit | edit source]

In order to display web pages to our site visitors, we are going to employ Nginx, a modern, efficient web server.

All of the software we will be using for this procedure will come directly from Debian's default package repositories. This means we can use the apt package management suite to complete the installation.

Since this is our first time using apt for this session, we should start off by updating our local package index. We can then install the server:

sudo apt-get install nginx ufw

On Debian 8, Nginx is configured to start running upon installation.

If you have the ufw firewall running, you will need to allow connections to Nginx. You should enable the most restrictive profile that will still allow the traffic you want. Since we'll want SSL, we will only need to allow traffic from ports 80 and 443.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 35240

We'll need to enable the firewall.

sudo ufw enable

Install MySQL[edit | edit source]

Install mysql-server package.

sudo apt-get install mysql-server

Run the secure installation, set a decent password.

sudo mysql_secure_installation
Change the root password? [Y/n] n
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

Install PHP for Processing[edit | edit source]

Install the php5-fpm and php5-mysql modules:

sudo apt-get install php5-fpm php5-mysql

Open the main php-fpm configuration file with root privileges:

sudo nano /etc/php5/fpm/php.ini

Look in the file for the parameter that sets cgi.fix_pathinfo. This will be commented out with a semi-colon (;) and set to "1" by default.

This is an extremely insecure setting because it tells PHP to attempt to execute the closest file it can find if the requested PHP file cannot be found. This basically would allow users to craft PHP requests in a way that would allow them to execute scripts that they shouldn't be allowed to execute.

We will change both of these conditions by uncommenting the line and setting it to "0" like this:


Save and close the file when you are finished. Now, we just need to restart our PHP processor by typing:

sudo systemctl restart php5-fpm

Configure Nginx to Use the PHP Processor[edit | edit source]

Now, we have all of the required components installed. The only configuration change we still need is to tell Nginx to use our PHP processor for dynamic content.

We do this on the server block level (server blocks are similar to Apache's virtual hosts). Open the default Nginx server block configuration file by typing:

sudo nano /etc/nginx/conf.d/default.conf

Make it look like this.

# Redirect all http traffic to https
server {	
	listen 80 default_server;
	listen [::]:80 default_server;
	server_name www.example.com example.com;
	include snippets/secure-headers.conf;
	return 301 https://www.example.com$request_uri;

# General configuration
server {
	listen 443 ssl http2;
	listen [::]:443 ssl http2;
	server_name www.example.com;
	include snippets/secure-headers.conf;

	ssl on;
	include snippets/ssl-params.conf;
	include snippets/ssl-example.com.conf;

	root /usr/share/nginx/html;
	index index.php index.html index.htm index.nginx-debian.html;

	location / {
		try_files $uri $uri/ =404;

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_pass unix:/var/run/php5-fpm.sock;

	location ~ /\.ht {
		deny all;

	location ~ /.well-known {
		allow all;


Enable Self-signing SSL Certificate[edit | edit source]

Install Certbot, the Let's Encrypt Client[edit | edit source]

The first step to using Let's Encrypt to obtain an SSL certificate is to install the certbot Let's Encrypt client on your server.

The certbot package was not available when Debian 8 was released. To access the certbot package, we will have to enable the Jessie backports repository on our server. This repository can be used to install more recent versions of software than the ones included in the stable repositories.

Add the backports repository to your server by typing:

echo 'deb http://ftp.debian.org/debian jessie-backports main' | sudo tee /etc/apt/sources.list.d/backports.list

After adding the new repository, update the apt package index to download information about the new packages:

sudo apt-get update

Once the repository is updated, you can install the certbot package by targeting the backports repository:

sudo apt-get install certbot -t jessie-backports

The certbot client should now be ready to use.

Obtain an SSL Certificate[edit | edit source]

Let's Encrypt provides a variety of ways to obtain SSL certificates, through various plugins.

We'll use the Webroot plugin to obtain an SSL certificate.

How To Use the Webroot Plugin[edit | edit source]

The Webroot plugin works by placing a special file in the /.well-known directory within your document root, which can be opened (through your web server) by the Let's Encrypt service for validation. Depending on your configuration, you may need to explicitly allow access to the /.well-known directory.

If you want a single cert to work with multiple domain names (e.g. example.com and www.example.com), be sure to include all of them. Also, make sure that you replace example.com with the appropriate domain name(s):

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

After certbot initializes, you will be prompted to enter your email and agree to the Let's Encrypt terms of service. Afterwards, the challenge will run. If everything was successful, you should see an output message that looks something like this:

 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert
   will expire on 2017-09-05. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
 - If you lose your account credentials, you can recover through
   e-mails sent to [email protected]
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

You will want to note the path and expiration date of your certificate, which was highlighted in the example output.

Certificate Files[edit | edit source]

After obtaining the cert, you will have the following PEM-encoded files:

  • cert.pem: Your domain's certificate
  • chain.pem: The Let's Encrypt chain certificate
  • fullchain.pem: cert.pem and chain.pem combined
  • privkey.pem: Your certificate's private key

It's important that you are aware of the location of the certificate files that were just created, so you can use them in your web server configuration. The files themselves are placed in a subdirectory in /etc/letsencrypt/archive. However, Let's Encrypt creates symbolic links to the most recent certificate files in the /etc/letsencrypt/live/your_domain_name directory. Because the links will always point to the most recent certificate files, this is the path that you should use to refer to your certificate files.

You can check that the files exist by running this command (substituting in your domain name):

sudo ls -l /etc/letsencrypt/live/your_domain_name

The output should be the four previously mentioned certificate files. In a moment, you will configure your web server to use fullchain.pem as the certificate file, and privkey.pem as the certificate key file.

Generate Strong Diffie-Hellman Group[edit | edit source]

To further increase security, you should also generate a strong Diffie-Hellman group. To generate a 2048-bit group, use this command:

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

This may take a few minutes but when it's done you will have a strong DH group at /etc/ssl/certs/dhparam.pem.

Configure TLS/SSL on Web Server (Nginx)[edit | edit source]

Now that you have an SSL certificate, you need to configure your Nginx web server to use it.

We will make a few adjustments to our configuration:

  1. We will create a configuration snippet containing our SSL key and certificate file locations.
  2. We will create a configuration snippet containing strong SSL settings that can be used with any certificates in the future.
  3. We will adjust the Nginx server blocks to handle SSL requests and use the two snippets above.

This method of configuring Nginx will allow us to keep clean server blocks and put common configuration segments into reusable modules.

Create a Configuration Snippet Pointing to the SSL Key and Certificate[edit | edit source]

Make a snippets directory which will contain common things like headers and general ssl configuration.

sudo mkdir /etc/nginx/snippets

Navigate to the snippet directory.

cd /etc/nginx/snippets

To properly distinguish the purpose of this file, we will name it ssl- followed by our domain name, followed by .conf on the end:

sudo nano ssl-example.com.conf

Within this file, we just need to set the ssl_certificate directive to our certificate file and the ssl_certificate_key to the associated key. In our case, this will look like this:

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

When you've added those lines, save and close the file.

Create a Configuration Snippet with Strong Encryption Settings[edit | edit source]

Next, we will create another snippet that will define some SSL settings. This will set Nginx up with a strong SSL cipher suite and enable some advanced features that will help keep our server secure.

The parameters we will set can be reused in future Nginx configurations, so we will give the file a generic name:

sudo nano ssl-params.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver valid=300s;
resolver_timeout 5s;

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

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

Save and close the file when you are finished.

Create a Configuration Snippet of secure headers[edit | edit source]

sudo nano secure-headers.conf
server_tokens off;

add_header X-Frame-Options SAMEORIGIN;

add_header X-Content-Type-Options nosniff;

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

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.g$

add_header Access-Control-Allow-Methods "GET, POST";
if ( $request_method !~ ^(GET|POST)$ ) {
        return 405;

add_header Set-Cookie "path=/; domain=.vikingsoftware.org; secure; HttpOnly" always;

Save and close the file when you are finished.

Create a Configuration Sippet of gzip compression[edit | edit source]

sudo nano gzip.conf
gzip on;

gzip_buffers            16 8k;
gzip_comp_level         6;
gzip_min_length         1024;
gzip_http_version       1.0;
gzip_proxied            expired no-cache no-store private auth;
gzip_types              text/plain text/css application/json application/x-javascript text/xml application/xml applic$
gzip_vary               on;

Now edit /etc/nginx/nginx.conf, remove #gzip on; and replace it with...

include snippets/gzip.conf

Enabling the Changes in Nginx[edit | edit source]

Now that we've made our changes and adjusted our firewall, we can restart Nginx to implement our new changes.

First, we should check to make sure that there are no syntax errors in our files. We can do this by typing:

sudo nginx -t

If everything is successful, you will get a result that looks like this:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If your output matches the above, your configuration file has no syntax errors. We can safely restart Nginx to implement our changes:

sudo systemctl restart nginx

The Let's Encrypt TLS/SSL certificate is now in place and the firewall now allows traffic to port 80 and 443. At this point, you should test that the TLS/SSL certificate works by visiting your domain via HTTPS in a web browser.

You can use the Qualys SSL Labs Report to see how your server configuration scores:

In a web browser:

This may take a few minutes to complete. The SSL setup in this guide should report at least an A rating.

Set Up Auto Renewal[edit | edit source]

Let’s Encrypt certificates are valid for 90 days, but it’s recommended that you renew the certificates every 60 days to allow a margin of error. At the time of this writing, automatic renewal is still not available as a feature of the client itself, but you can manually renew your certificates by running the Let’s Encrypt client with the renew option.

To trigger the renewal process for all installed domains, run this command:

sudo certbot renew

Because we recently installed the certificate, the command will only check for the expiration date and print a message informing that the certificate is not due to renewal yet. The output should look similar to this:

Saving debug log to /var/log/letsencrypt/example.com.log

Processing /etc/letsencrypt/renewal/example.com.conf
Cert not yet due for renewal

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/example.com/fullchain.pem (skipped)
No renewals were attempted.

Notice that if you created a bundled certificate with multiple domains, only the base domain name will be shown in the output, but the renewal should be valid for all domains included in this certificate.

A practical way to ensure your certificates won’t get outdated is to create a cron job that will periodically execute the automatic renewal command for you. Since the renewal first checks for the expiration date and only executes the renewal if the certificate is less than 30 days away from expiration, it is safe to create a cron job that runs every week or even every day, for instance.

Let's edit the crontab to create a new job that will run the renewal command every week. To edit the crontab for the root user, run:

sudo crontab -e

If this is your first time using crontab, you may be asked to select your preferred text editor. If you have no strong preference, nano is an easy choice.

Add the following line:

30 2 * * * /usr/bin/certbot renew --noninteractive --renew-hook "/bin/systemctl reload nginx" >> /var/log/le-renew.log

Save and exit. This will create a new cron job that will execute the certbot renew command every day at 2:30 am, and reload Nginx if a certificate is renewed. The output produced by the command will be piped to a log file located at /var/log/le-renewal.log.