How to Install Free SSL Certificates from Let's Encrypt on a Linux Server

These are my notes for installing and auto-renewing free TLS/SSL certificates from Let's Encrypt on a LAMP server. The instructions are correct for CentOS 7.2 and Apache 2.4.

You will need to know how to SSH into your box, and your Linux user account will need sudo privileges. Read how to manage Linux user accounts. Also, you should already have installed a basic LAMP stack on the server.

These instructions assume that the web application that you are securing is served from the domain www.example.com – just replace that with your own – and that the domain is already configured with A or CNAME records to route to your server. You should have configured name-based virtual host routing for the application's domain, but only for port 80 and not HTTPS's port 443.

Install extra packages

You will need to install some packages that are not available from CentOS's native package repository, but which are available from the EPEL (Extra Packages for Enterprise Linux) repository. Enable the EPEL repository if you have not done so already.

sudo yum install epel-release

There are a couple of packages to install: mod_ssl, which is an extension for the Apache HTTP Server, and python-certbot-apache, which is a client for the Let's Encrypt service.

sudo yum install mod_ssl python-certbot-apache

Firewalls

If you are using FirewallD to manage your network traffic rules, run the following commands to enable port 443 for HTTPS, and to make the change permanent.

sudo firewall-cmd --add-service=https
sudo firewall-cmd --runtime-to-permanent

If you edit iptables directly, use the following comand to enable port 443 for HTTPS traffic.

sudo iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT

On installation, the mod_ssl package configures a self-signed certificate and installs it on your server. So you should already be able to access your web application over HTTPS. Test with curl, using the -k flag to accept certificates that are not signed by a trusted Certificate Authority. The output should be the HTML for your application's homepage.

curl -k https://www.example.com

Get an SSL certificate from Let's Encrypt

Run the following command to begin the process of generating and installing a free SSL certificate from the Let's Encrypt service.

sudo certbot --apache

You will be prompted for information about the domain names that you wish to secure with the SSL certificate. Cleverly, the certbot application will read Apache's configuration and present you with a list of ServerName and ServerAlias values that are configured for port 80 but which are not yet configured for port 443. You may protect multiple domains and sub-domains with a single SSL certificate. It is recommended that the first domain in the certificate's list be the web application's primary domain (e.g. www.example.com) followed by any aliases (e.g. example.com) and associated subdomains (e.g. static.example.com).

Follow the remainder of certbot's instructions to complete the configuration of the SSL certificate. You will be able to choose between enabling both HTTP and HTTPS access to your web application, or the more secure option of having standard HTTP requests redirected to HTTPS. Choose the more secure option, unless you have specific needs to serve some of your application's resources over standard HTTP. It is good practice to enable HTTPS as a blanket policy across all of an application's resources.

At the end of the process you will be presented with information about your SSL certificate, including where the files are and when the certificate will expire.

Checks

The Let's Encrypt certbot will have modified Apache's configuration files and enabled your domains for HTTPS. I like to tidy-up all of that so that I'm left with a single virtual host configuration file per application, something like this:

<VirtualHost *:80>

    ServerName www.example.com
    ServerAlias    example.com

    RewriteEngine on
    RewriteCond %{SERVER_NAME} =www.example.com [OR]
    RewriteCond %{SERVER_NAME} =example.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]

</VirtualHost>

<IfModule mod_ssl.c>
    <VirtualHost *:443>

        ServerName www.example.com
        ServerAlias    example.com

        ServerAdmin [email protected]

        DocumentRoot /var/www/www.example.com/public/

        SetEnv APPLICATION_HOST production

        LogLevel error
        ErrorLog /var/www/www.example.com/private/logs/apache/error.log
        CustomLog /var/www/www.example.com/private/logs/apache/access.log common

        <FilesMatch \.php$>
            SetHandler "proxy:unix:/var/run/php-fpm/www.example.com.sock|fcgi://www.e$
        </FilesMatch>

        <Proxy fcgi://www.example.com>
            ProxySet connectiontimeout=5 timeout=240
        </Proxy>

        <Directory "/var/www/www.example.com/public/">
            Require all granted
            AllowOverride All
            Options All -Indexes
            <IfModule dir_module>
                DirectoryIndex index.html index.php
            </IfModule>
        </Directory>

        SSLEngine on

        SSLCertificateFile /etc/letsencrypt/live/www.example.com/cert.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/www.example.com/privkey.pem
        SSLCertificateChainFile /etc/letsencrypt/live/www.example.com/chain.pem

        # HSTS:
        Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains"
        Header always set X-Frame-Options DENY
        Header always set X-Content-Type-Options nosniff

        # Apply the Secure flag to all cookies
        Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"

    </VirtualHost>
</IfModule>

Restart the server for the changes to take full effect.

sudo systemctl restart httpd

Reload your web application using the https:// scheme. Information on the new SSL certificate will be available via your web browser's address bar. There are also a couple of useful online tools where you can verify the status of your SSL certificate and review the level of security that your SSL configuration provides:

To get a perfect A+ score, you should harden your SSL configuration.

Hardening

Apache's default SSL configuration can be adjusted for better security. Open Apache's main configuration SSL configuration file.

sudo nano /etc/httpd/conf.d/ssl.conf

Find the SSLProtocol and SSLCipherSuite lines and comment them out.

# SSLProtocol all -SSLv2
# SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA

At the very bottom of the file, after the closing of the <VirtualHost> block, enter the following:

SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
SSLProtocol All -SSLv2 -SSLv3
SSLHonorCipherOrder On

SSLCompression off 
SSLUseStapling on 
SSLStaplingCache "shmcb:logs/stapling-cache(150000)" 

# Requires Apache >= 2.4.11
#SSLSessionTickets Off

The last directive, SSLSessionTickets, is understood only by Apache >= 2.4.11. Earlier releases will fail if this directive is included in the server's configuration. Check which version of Apache you are running with the command httpd -v.

Check your configuration for syntax errors and then, if the syntax is OK, restart the Apache service.

sudo apachectl configtest
sudo systemctl restart httpd

Re-test your web application against the Qualys SSL Server Test tool. This configuration should give you a perfect A+ score. Be aware that this configuration is compatible with modern A grade browsers but not legacy clients such as Internet Explorer 6.

Auto-renewing

Let's Encrypt certificates are valid for only 90 days and can be renewed within 30 days of their expiry date. Use the following command to trigger the renew process for all installed domains.

sudo certbot renew

Any certificates that are within their renewal window will be renewed automatically. It'll be a noop for certificates that are not yet due for renewal.

Create a cron job to periodically execute this command. It is perfectly OK to run the task every day, at a time when your server is the least busy. The job should be assigned to the root user, since root privileges are required for the certbot renew command.

sudo crontab -e

The following configuration runs certbot renew every day at 1:30am, and the output is printed to a log file.

# Minute  Hour     DayOfMonth  Month  DayOfWeek  Command
# 0-59    0-23     1-31        1-12   0-6

30        1        *           *      *          /usr/bin/certbot renew >> /var/log/letsencrypt-renew.log