HTTPS with Letsencrypt on nginx

Open port 443 on Shorewall firewall using the ACCEPT rule /etc/shorewall/rules

# Allow access to web server default ports (secure and unsecured HTTP)
ACCEPT net $FW tcp 80
ACCEPT net $FW tcp 443

Download the certbot-auto program:

$ cd /etc/nginx
$ mkdir letsencrypt
$ cd letsencrypt
$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto

To obtain certificates for a domain, certbot will verify you have ownership by creating files in a hidden directory under the domain’s web root and issuing a HTTP request for them. To ensure nginx will serve the files, without denying access (403 Forbidden), then add the following to your nginx config. For instance, you may add the rule to the file /etc/nginx/custom-conf/restrictions.con before any rules restricting access to dot files:

# Allow the letsencrypt ACME Challenge.
    location ~ /\.well-known\/acme-challenge {
    allow all;
}

On some web applications that I run static files are served from /static/. For those domains I place the following location block inside the application’s server block:

# Allow the letsencrypt ACME Challenge.
location '/.well-known/acme-challenge' {
    root /var/www/www.paulpepper.com/html/letsencrypt;
    default_type 'text/plain';
    allow all;
}

Run certbot-auto, specifying the root from which files for domains are served from and the name of those domains that the requested certificate applies to:

$ sudo ./certbot-auto certonly --agree-tos --webroot -w /var/www/www.paulpepper.com/html -d paulpepper.com -d www.paulpepper.com

If successful, then new key and certificate files should have been created under /etc/letsencrypt/live/paulpepper.com after running the above command.

Next configure nginx to service HTTPS requests for the domains using the new certificate. In the virtual server config:

server {
    listen 443 ssl;
    server_name paulpepper.com;

    root /var/www/www.paulpepper.com/html;

    include custom-conf/restrictions.conf;

    ssl_certificate /etc/letsencrypt/live/paulpepper.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/paulpepper.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/paulpepper.com/chain.pem;
}

You might also wish to redirect HTTP requests to HTTPS too:

server {
    listen 443 ssl;
    server_name www.paulpepper.com;

    root /var/www/www.paulpepper.com/html;

    include custom-conf/restrictions.conf;

    ssl_certificate /etc/letsencrypt/live/paulpepper.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/paulpepper.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/paulpepper.com/chain.pem;
}

server {
    listen 80;
    server_name paulpepper.com www.paulpepper.com;
    return 301 https://www.paulpepper.com$request_uri;
}

server {
    listen 443 ssl;
    server_name paulpepper.com;
    return 301 https://www.paulpepper.com$request_uri;
}

Check your config and restart nginx

$ sudo nginx -t
...
$ sudo systemctl restart nginx.service

If your distribution is still using the Upstart init system then restart nginx as follows:

$ sudo service nginx restart

The certbot documentation recommends running a cron job twice per day to renew certificates. Let’s Encrypt will only renew certificates if they are due to expire, so it’s safe and good practice to run the renewal frequently.

Create a new file under /etc/cron.d/ called letsencrypt with the following content:

 # Run the letsencrypt renewal service using certbot-auto.
 8 06,18 * * * root /etc/nginx/letsencrypt/certbot-auto renew --quiet --no-self-upgrade --post-hook "/usr/bin/service nginx reload" >> /var/log/letsencrypt/renewal.log

The above crontab entry will attempt to run the certbot-auto renewal twice per day at 6:08am and 6:08pm, and then reload nginx if ssl certificates were due for renewal (the –post-hook only executes its argument if certificates were due for renewal).

WordPress content that uses the old http protocol prefix can be replaced using an SQL UPDATE. Here’s how you might do that:

$ mysqldump -u root -p paulpepper > paulpepper-db-backup.sql
$ mysql -u root -p
...
mysql> CONNECT paulpepper;
mysql> UPDATE `wp_posts` SET `post_content` = REPLACE(`post_content`, 'http://www.paulpepper.com', 'https://www.paulpepper.com');
mysql> UPDATE `wp_posts` SET `post_content` = REPLACE(`post_content`, 'http://paulpepper.com', 'https://paulpepper.com');

The table wp_posts is the one containing content for pages and post, but may be named slightly depending upon the value of the $table_prefix variable found in your wp-config.php file. The standard value given to this variable is ‘wp_’ and so wp_posts is normally table that should be targeted by the above SQL.

Tags: ,