How To Secure Apache with mod_md Let’s Encrypt on Ubuntu 20.04 LTS

How do I secure Apache with mod_md Let’s Encrypt on Ubuntu 20.04 and obtain a free TLS/SSL certificate? How do I secure Apache 2 with Let’s Encrypt on Ubuntu 20.04 LTS server?

Apache server comes with a module named mod_md. We can use this for certificate provisioning via the ACME protocol. This page explains how to install, set up and configure Apache with a mod_md module to secure traffic with Let’s Encrypt free TLS/SSL certificate on Ubuntu 20.04 LTS server.
Tutorial requirements
Operating system/appUbuntu Linux with Apache 2
Root privileges required Yes
Difficulty Easy (rss)
Estimated completion time 15m
Table of contents

How to Secure Apache with mod_md Let’s Encrypt on Ubuntu 20.04

Let’s Encrypt is a CA that follows the ACME protocol. One can use Let’s Encrypt to issue free TLS/SSL certificates for Apache, Nginx, and other servers. In this tutorial, you will use mod_md to obtain a free TLS/SSL certificate for Apache 2 on Ubuntu 20.4 and set up your certificate to renew automatically too. Our sample set up is as follows:

  • Domain – www42.cyberciti.biz
  • HTTPS port – 443
  • Virtual domain config file – /etc/apache2/sites-available/www42.cyberciti.biz.conf

Make sure Apache installed by following How to install Apache on Ubuntu 20.04 guide.

Step 1 – Installing mod_md for Let’s Encrypt

First, apply updates using the apt command:
sudo apt update
sudo apt upgrade

Then, install the mod_md by typing the following command:
sudo apt install libapache2-mod-md

Enabling mod_md on Ubuntu 20.04 LTS

Turn on mod_md, type:
sudo a2enmod md
Sample outputs:

Enabling module md.
To activate the new configuration, you need to run:
  systemctl restart apache2

Make sure you activate the mod_ssl too, run:
sudo a2enmod ssl
Outputs:

Considering dependency setenvif for ssl:
Module setenvif already enabled
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
See /usr/share/doc/apache2/README.Debian.gz on how to configure SSL and create self-signed certificates.
To activate the new configuration, you need to run:
  systemctl restart apache2

Apache 2 must be reloaded or restarted with the help of the systemctl command”
sudo systemctl reload apache2.service

Step 2 – Set up the SSL certificate

Make sure your Apache 2 is working and listens on port 80. Verify using the ss command or netstat command:
sudo netstat -tulpn | grep ':80'
## or ##
sudo ss -tulpn | grep ':80'

Sample outputs:

tcp    LISTEN  0       128                        *:80                  *:*      users:(("apache2",pid=2550,fd=4),("apache2",pid=2549,fd=4),("apache2",pid=2548,fd=4))

All clients must connect to your server over port 80. Otherwise, you will not get validated for Let’s Encrypt certificate. From your desktop, run:
curl -I http://www42.cyberciti.biz
curl command outputs validting that we can connect to the port TCP port 80:

HTTP/1.1 200 OK
Date: Wed, 06 May 2020 19:30:43 GMT
Server: Apache/2.4.41 (Ubuntu)
Last-Modified: Wed, 06 May 2020 19:15:29 GMT
ETag: "15e-5a4ff965902a3"
Accept-Ranges: bytes
Content-Length: 350
Vary: Accept-Encoding
Connection: close
Content-Type: text/html

Let us edit the /etc/apache2/sites-available/www42.cyberciti.biz.conf, enter:
sudo nano /etc/apache2/sites-available/www42.cyberciti.biz.conf
At the top of file add the following three mod_md directives:

## Secure Apache with mod_md Let's Encrypt directives ##
ServerAdmin webmaster@cyberciti.biz
MDCertificateAgreement accepted
MDomain www42.cyberciti.biz
MDPrivateKeys RSA 4096

Where,

  • ServerAdmin webmaster@cyberciti.biz : mod_md will use this email address when registering your domains at Let’s Encrypt.
  • MDCertificateAgreement accepted : You must accept the Terms of Service conditions as set by Let’s Encrypt.
  • MDomain www42.cyberciti.biz : Declare a domain named that should be manged by mod_md to issue and renew certificates. You can use full domain name such as www.cyberciti.biz or cyberciti.biz or www42.cyberciti.biz. Make sure it matches to ServerName.
  • MDPrivateKeys RSA 4096 : Set type and size of the private keys generated.

Here is my complete config file :

## Apache with mod_md Let's Encrypt ##
## mod_md config for Let's Encrypt ##
ServerAdmin webmaster@cyberciti.biz
MDCertificateAgreement accepted
MDomain www42.cyberciti.biz
MDPrivateKeys RSA 4096
 
## HTTP port 80 config ##
<VirtualHost *:80>
    ServerAdmin webmaster@cyberciti.biz
    ServerName www42.cyberciti.biz
    DocumentRoot /home/cyberciti.biz/html
    DirectoryIndex index.html
    ErrorLog ${APACHE_LOG_DIR}/www42.cyberciti.biz-error.log
    CustomLog ${APACHE_LOG_DIR}/www42.cyberciti.biz-access.log combined
    # Redirect all http requests to HTTPS (uncomment the following two lines when HTTPS issued) 
    # RewriteEngine On
    # RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>
 
## HTTPS Config ##
<VirtualHost *:443>
    SSLEngine on
    ServerAdmin webmaster@cyberciti.biz
    ServerName www42.cyberciti.biz
    DocumentRoot /home/cyberciti.biz/html
    DirectoryIndex index.html
    ErrorLog ${APACHE_LOG_DIR}/www42.cyberciti.biz-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/www42.cyberciti.biz-ssl-access.log combined
    # Turn on HTTP/2 
    Protocols h2 http/1.1
    # Set HTTP Strict Transport Security
    Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>
## Only enable TLS v1.3 and avoid older protocols ##
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
SSLHonorCipherOrder     off
SSLSessionTickets       off
 
## Turn on OCSP Stapling ##
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
 
## Permission for our DocumentRoot  ##
<Directory /home/cyberciti.biz/html>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

Turn on Apache’s mod_rewrite and mod_headers under Ubuntu

We have already declared a rewrite rule in our config, so we need mod_rewrite. Hence, type the following command:
sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2

Turn on mod_headers too:
sudo a2enmod headers
Enabling module headers.
To activate the new configuration, you need to run:
systemctl restart apache2

Check for config errors, run:

sudo apache2ctl configtest
Syntax OK

Step 3 – Open HTTPS (TCP port 443) using the UFW firewall on Ubuntu

Run the following ufw command to open TCP port 443 for everyone:
sudo ufw allow 443/tcp comment 'accept secure Apache connections'
Verify it rules:
sudo ufw status
See “How To Configure Firewall with UFW on Ubuntu 20.04 LTS” for more info.

Step 4 – Obtaining an SSL certificate using mod_md

So far, we installed mod_md for Apache on Ubuntu, turned on all essential modules, and open required TCP ports using a firewall. It is time to restart our Apache 2 server to obtain the free TLS/SSL certificate using Let’s Encrypt certificate authority (CA). Therefore, restart the Apache 2 server:
sudo systemctl restart apache2.service
As soon as Apache 2 restarted mod_md will contact Let’s Encrypt and request a certificate for your domain. Typically it takes up to one minute. You can check the server error log or Apache’s mod_status page to see if the request was successful or not. Here is what you will see in erro.log file:
sudo tail -f /var/log/apache2/error.log
One can use the grep command too:
sudo grep 'The Managed Domain' /var/log/apache2/error.log
Sample outputs indicating that LE has issued us a free TLS/SSL certificate:

[Wed May 06 20:17:38.112849 2020] [md:notice] [pid 21777:tid 139807872861952] AH10059: The Managed Domain www42.cyberciti.biz has been setup and changes will be activated on next (graceful) server restart.

Of course we can visit server-status url too. For example:
http://www42.cyberciti.biz/server-status
http://your-public-ip-here/server-status

Click to enlarge

A graceful Apache 2 server restart now is recommended to activate the certificate:
sudo systemctl reload apache2.service

Step 5 – Test secure Apache 2 connection

All you have to do is type the following command or use a web browser such as Firefox/Chrome to make sure you are getting HTTPS connection:
curl -I https://www42.cyberciti.biz

curl command in action verify that our HTTPS connection is working and traffic is secured and encrypted

Here is outputs from SSL Lab’s test:

My secure Apache server with Let’s Encrypt TLS/SSL running on Ubuntu 20.04 LTS and verified by SSL Lab’s test

Step 6 – Automatically renewing an SSL certificate using mod_md and watchdog_module

The mod_md uses mod_watchdog module, which provides programmatic hooks for other modules to run tasks such as renewing TLS/SSL certificates and more periodically. In other words, auto-renew mode requires mod_watchdog to be active in your server. Hence, verify that mod_watchdog is activated using the following command:
sudo apache2ctl -M
sudo apache2ctl -M | grep -i watchdog

Loaded Modules:

 core_module (static)
 so_module (static)
 watchdog_module (static)
 http_module (static)
 unixd_module (static)
 access_compat_module (shared)
 alias_module (shared)
 auth_basic_module (shared)
 authn_core_module (shared)
 ....
 ..
 ...
 ssl_module (shared)
 status_module (shared)

Step 7 – Monitoring certificate status

Now we set up Apache with mod_md and got a free TLS/SSL from Let’s Encrypt. It is time to monitor status of our certificate. There are two ways. First open /server-status URL:
https://www42.cyberciti.biz/server-status
https://your-public-ip-here/server-status

Click to enlarge

Edit your server config file, run:
sudo nano /etc/apache2/sites-available/www42.cyberciti.biz.conf
Append the following:
<Location "/md-status">
  SetHandler md-status
</Location>

Save and close the file. Restart the server and run:
sudo systemctl restart apache2.service
curl https://www42.cyberciti.biz/md-status

Sample outputs:

{
  "version": "2.0.10",
  "managed-domains": [
    {
      "name": "www42.cyberciti.biz",
      "domains": [
        "www42.cyberciti.biz",
        "www43.cyberciti.biz"
      ],
      "contacts": [
        "mailto:webmaster@cyberciti.biz"
      ],
      "transitive": 1,
      "ca": {
        "proto": "ACME",
        "url": "https://acme-v02.api.letsencrypt.org/directory",
        "agreement": "accepted"
      },
      "state": 2,
      "renew-mode": 1,
      "renew-window": "33%",
      "warn-window": "10%",
      "must-staple": false,
      "cert": {
        "valid-from": "Wed, 06 May 2020 19:17:37 GMT",
        "valid-until": "Tue, 04 Aug 2020 19:17:37 GMT",
        "serial": "040E339A0A7D2224819A550BBB4596279F67",
        "sha256-fingerprint": "d78933fa946cb71810111876049defa4feb6820c319c69918ba925b463bbd11c"
      },
      "renew": false
    }
  ]
}

Where are my TLS/SSL certificate files located on disk?

You need to cd into the /etc/apache2/md directory:
$ sudo -i
# cd /etc/apache2/md/
# ls -l

We will see file as follows:

total 28
drwxr-xr-x 3 root     root 4096 Nov 13 13:02 accounts
drwx------ 3 root     root 4096 Nov 13 13:02 archive
drwxr-xr-x 2 www-data root 4096 Nov 13 13:02 challenges
drwx------ 3 root     root 4096 Nov 13 13:02 domains
-rw------- 1 root     root  116 Nov 13 12:52 md_store.json
drwxr-xr-x 2 www-data root 4096 Nov 13 13:02 staging
drwx------ 2 root     root 4096 Nov 13 13:02 tmp

Make sure you look into the domains directory:
# cd domains
# ls -l
# cd www42.cyberciti.biz
# ls -l

Here are TLS/SSL files for your domain:

total 16
-rw------- 1 root root 3997 Nov 13 13:02 job.json
-rw------- 1 root root  492 Nov 13 13:04 md.json
-rw------- 1 root root 3272 Nov 13 13:02 privkey.pem
-rw------- 1 root root 3916 Nov 13 13:02 pubcert.pem

Issuing www and non-www domains

Want TLS/SSL certificate for both cyberciti.biz and www.cyberciti.biz domain? Try it as follows (note MDomain and ServerAlias directives added/updated):

ServerAdmin webmaster@cyberciti.biz
MDCertificateAgreement accepted
# www and non-www TLS certicate #
MDomain cyberciti.biz www.cyberciti.biz
MDPrivateKeys RSA 4096
 
## HTTP port 80 config ##
<VirtualHost *:80>
    ServerAdmin webmaster@cyberciti.biz
    ServerName www.cyberciti.biz
    ServerAlias cyberciti.biz
    DocumentRoot /home/cyberciti.biz/html
    DirectoryIndex index.html
    ErrorLog ${APACHE_LOG_DIR}/cyberciti.biz-error.log
    CustomLog ${APACHE_LOG_DIR}/cyberciti.biz-access.log combined
    # Redirect all http requests to HTTPS (uncomment the following two lines when HTTPS issued) 
    # RewriteEngine On
    # RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>
 
## HTTPS Config ##
<VirtualHost *:443>
    SSLEngine on
    ServerAdmin webmaster@cyberciti.biz
    ServerName www.cyberciti.biz
    ServerAlias cyberciti.biz
    DocumentRoot /home/cyberciti.biz/html
    DirectoryIndex index.html
    ErrorLog ${APACHE_LOG_DIR}/cyberciti.biz-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/cyberciti.biz-ssl-access.log combined
    # Turn on HTTP/2 
    Protocols h2 http/1.1
    # Set HTTP Strict Transport Security
    Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>
## Only enable TLS v1.3 and avoid older protocols ##
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
SSLHonorCipherOrder     off
SSLSessionTickets       off
 
## Turn on OCSP Stapling ##
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
 
## Permission for our DocumentRoot  ##
<Directory /home/cyberciti.biz/html>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

A note about ‘auto’ directive

The above approach work for one or two subdomains. But what if you have 4 or 5 domains? Such as cyberciti.biz, www.cyberciti.biz, forum.cyberciti.biz, webmail.cyberciti.biz? Then we can use the auto keyword as follows:

MDomain cyberciti.biz auto
 
<VirtualHost *:443>
    ServerName cyberciti.biz
    ServerAlias www.cyberciti.biz
    ServerAlias forum.cyberciti.biz
    ServerAlias webmail.cyberciti.biz
    ...
</VirtualHost>

Whenever you add more ServerAlias names to this virtual host with ‘auto’ keyword, they will be added as well to the Managed Domain. If you prefer to explicitly declare all the domain names, use ‘manual’ mode.

Multiple domains

Here is how to set up nixcraft.com and cyberciti.biz domains:

 
MDomain cyberciti.biz auto
MDomain nixcraft.com auto
 
# cyberciti.biz #
<VirtualHost *:80>
    ServerName cyberciti.biz
    ServerAlias www.cyberciti.biz
    ...
    .....
</VirtualHost>
<VirtualHost *:443>
    ServerName cyberciti.biz
    ServerAlias www.cyberciti.biz
    ...
</VirtualHost>
 
# nixcraft.com #
<VirtualHost *:80>
    ServerName nixcraft.com
    ServerAlias www.nixcraft.com
    ...
</VirtualHost>
<VirtualHost *:443>
    ServerName nixcraft.com
    ServerAlias www.nixcraft.com
    ...
</VirtualHost>

Conclusion

In this tutorial, you learned how to secure Apache with mod_md Let’s Encrypt module to issue and auto-renew free TLS/SSL certificate on Ubuntu 20.04 LTS Linux server. For more information, see mod_md documentation here. Keep reading read of the series:

This entry is 2 of 2 in the Linux, Apache, MySQL, PHP (LAMP) stack on Ubuntu 20.04 Tutorial series. Keep reading the rest of the series:
  1. How to install Apache on Ubuntu 20.04 LTS
  2. Secure Apache with mod_md Let’s Encrypt on Ubuntu 20.04 LTS

🐧 Please support my work on Patreon or with a donation.
🐧 Get the latest tutorials on Linux, Open Source & DevOps via:
CategoryList of Unix and Linux commands
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network Utilitiesdig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04
12 comments… add one
  • Sima Chavda Aug 12, 2020 @ 9:09

    I am getting the following error when installing Let’s Encrypt SSL on my domain:

    Waiting for verification…
    Challenge failed for domain linuxbuz.com
    dns-01 challenge for linuxbuz.com
    Cleaning up challenges
    Some challenges have failed.

    • 🐧 Vivek Gite Aug 12, 2020 @ 11:40

      Are you using dns challenge? This tutorial only works with http challenge.

  • Frederic Sep 4, 2020 @ 15:11

    Thanks a lot, and great tutorial, anyway I’m not sure to understand about the renew. I mean, is this automatic renw ? even without any crontab ?

    Thanks

    • 🐧 Vivek Gite Sep 4, 2020 @ 15:17

      yes, watchdog_module will do auto renewal. There is no cron job since Apache with watchdog_module always running on port 80 and 443.

      • Frederic Sep 6, 2020 @ 22:52

        Thanks Vivek !

  • Abigail Sep 7, 2020 @ 6:13

    Hi,

    I am working with a PHP app. This post was a lifesaver. One question, how can I get A+ score in the test? Anyone willing to share httpd.conf settings?

  • Vitor Hugo Nunes dos Santos Nov 12, 2020 @ 23:07

    Hi,

    where does mod_md save the certificate? I couldn’t, for the life of me, figure out where it saves it following documentation and using locate(1).

    Best regards,

    vhns

    • 🐧 Vivek Gite Nov 13, 2020 @ 13:12

      The page has been updated with location of files. Thanks for the feedback.

  • Drakanor Nov 18, 2020 @ 15:00

    Your vhost example is only working for me, when setting MDMembers to “manual”. Else I’m getting:

    [Wed Nov 18 15:26:15.651155 2020] [mpm_prefork:notice] [pid 6864] AH00169: caught SIGTERM, shutting down
    [Wed Nov 18 15:26:15.756163 2020] [md:error] [pid 6962] (22)Invalid argument: CA url for [MYDOMAIN] invalid (missing uri scheme): accepted
    [Wed Nov 18 15:26:15.756217 2020] [md:error] [pid 6962] (22)Invalid argument: AH10073: synching 1 mds to registry
    AH00016: Configuration Failed

    I had to remove the *:80 part from the particular vhost config and do a general 80 to 443 redirect in 000-default.conf to get it working.

  • Francisco Vega Dec 8, 2020 @ 4:38

    Hi, thanks for Your Tutorial, keep your excellent work.

    Quick question:
    How can issue an cert for both example.com and http://www.example.com?

    I’m getting “certificate valid only for http://www.example.com

    • 🐧 Vivek Gite Dec 10, 2020 @ 14:58

      Update config as follows:

      MDomain example.com www.example.com

      Make sure VirtualHost has entries as follows:

         ServerName www.example.com
         ServerAlias example.com
      

      I added section too to deal with both www and non-www domains.

  • Mike Eddy Jan 16, 2021 @ 19:30

    Excellent tutorial, however I did not receive a certificate until I published the site

    a2ensite mysite

    I am not so familiar with Ubuntu, so maybe it sometimes is not necessary?

Leave a Reply

Your email address will not be published. Required fields are marked *

Use HTML <pre>...</pre> for code samples. Problem posting comment? Email me @ webmaster@cyberciti.biz