macOS 11.0 Big Sur Apache Setup: LetsEncrypt SSL

Third part in a multi-part blog series for Mac developers

9 minutes
Part 3: macOS 11.0 Big Sur Web Development Environment

In Part 1 of this 3-part series, we covered configuring Apache on macOS Big Sur 11.0 to work better with your local user account, as well as the installation process for installing multiple versions of PHP. In Part 2, we covered installing MySQL, Virtual Hosts, APC caching, YAML, and Xdebug.

In this Part 3, we will cover getting your site setup with LetsEncrypt SSL support for this setup.

11/27/2020 Added self-signed section back + vhost configuration
11/13/2020 Updated to reflect the release of macOS 11.0 Big Sur + switch from self-signed to LetsEncrypt Certificates

This guide is intended for experienced web developers. If you are a beginner developer, you will be better served using MAMP or MAMP Pro.

SSL Introduction & Requirements

In order to get SSL setup and running properly four our Homebrew-powered Apache setup, we need to first create an SSL Certificate. This used to be a pain and cost money for a valid SSL certificate, but thankfully we now have Let's Encrypt, a non-profit Certificate Authority that provides certificates to 225 million websites!

This does require that you have registered a domain name with a company such as Hover, GoDaddy, Network Solutions, etc. You should configure this so that a valid host is available to point to your local webserver. You will probably also need to setup port forwarding so your external IP address is routed internally to your webserver. Usually this consists of setting up a port forward for HTTP, HTTPS with ports 80 and 443 to the internal IP address of your computer.

This is a process that depends on several factors:

  1. Domain Registrar used to purchase your domain
  2. DNS services used which could be the same as the registrar, or another provider such as CloudFlare
  3. Type of internet connection be it static or dynamic IP address
  4. The actual external IP address of your internet connection
  5. Make and model of your router which will need to be setup to configure port forwarding

I'll leave this process up to you as it might require some Googling to get the this process setup depending all the factors listed above. A quick way to test if you have things setup is to try reaching host via your phone or some other device that is not on your internal network.

Let's assume you have registered the domain grav.rocks (p.s. I already own this one, you need to use your own!), and you have configured the host dev.grav.rocks to be port-forwarded to your local development machine. You have tested this and you can confirm that when you browse to http://dev.grav.rocks on your phone, you are indeed seeing the webserver of your internal network. You have successfully configured remote access, but now you want to get SSL working.

Throughout this guide I use dev.grav.rocks as an example host. You will need to replace this with your own hostname

Let's Encrypt + Certbot

Now you have a valid host that is accessible from the internet, we need to generate a valid LetsEncrypt SSL certificate. First we should install the certbot tool that will facilitate this process:

brew update
brew upgrade
brew install certbot

To be able to use certbot in a non-root setup (like we have with Brew), we need to create a cli.ini file so that the certbot command will use local paths rather than root access-only system paths:

Create a Certbot Config

mkdir -pv ~/.config/letsencrypt
code ~/.config/letsencrypt/cli.ini

This assumes you have Visual Studio Code installed and have enabled the CLI code command. See the first part of this series to find out more about this.

In this new file paste the following:

work-dir = /usr/local/etc/certbot 
logs-dir = /usr/local/etc/certbot/logs
config-dir = /usr/local/etc/certbot/certs

Save the file.

Create the Certificate

Now we can run certbot without requiring sudo which would limit our ability to run Apache as a non-root user.

certbot certonly --standalone

This kicks off a process that requires your response in a few places. Enter email address, agree to Terms of Services, Choose Y or N to join mailing list. Lastly when prompted, enter the name of the host you want to use, e.g. dev.grav.rocks.

Certbot will then try to authenticate by challenging the domain names provided:

Obtaining a new certificate
Performing the following challenges:
http-01 challenge for dev.grav.rocks
Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager

If successful, certbot will generate a fullchain.pem and a privkey.pem file that we can use to configure our SSL certificate in the Apache configuration. The output should look something like this:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /usr/local/etc/certbot/certs/live/dev.grav.rocks/fullchain.pem
   Your key file has been saved at:
   /usr/local/etc/certbot/certs/live/dev.grav.rocks/privkey.pem
   Your cert will expire on 2021-02-12. 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 renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /usr/local/etc/certbot/certs. 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:

The important parts to jot down are the full paths to the .pem files which we'll use in the following section:

Certificate: /usr/local/etc/certbot/certs/live/dev.grav.rocks/fullchain.pem
Key File: /usr/local/etc/certbot/certs/live/dev.grav.rocks/privkey.pem

Apache SSL Configuration

The first step is to make some modifications to your httpd.conf:

code /usr/local/etc/httpd/httpd.conf

In this file you should uncomment both the socache_shmcb_module, ssl_module, and also the include for the httpd-ssl.conf by removing the leading # symbol on those lines:

LoadModule socache_shmcb_module lib/httpd/modules/mod_socache_shmcb.so
...
LoadModule ssl_module lib/httpd/modules/mod_ssl.so
...
Include /usr/local/etc/httpd/extra/httpd-ssl.conf

Next we need to change the default 8443 port to the more standard 443 and comment out some sample code. So we need to open the SSL config file:

code /usr/local/etc/httpd/extra/httpd-ssl.conf

find:

Listen 8443

replace it with:

Listen 443

then find:

<VirtualHost _default_:8443>

#   General setup for the virtual host
DocumentRoot "/usr/local/var/www"
ServerName www.example.com:8443

and replace the 8443 references with 443 and update the DocumentRoot and ServerName based on the values you setup in your httpd.conf file:

<VirtualHost _default_:443>

#   General setup for the virtual host
DocumentRoot "/Users/your_user/Sites"
ServerName dev.grav.rocks:443

Next locate the Server Certificate: section add the location of your SSLCertificateFile you obtained above:

SSLCertificateFile "/usr/local/etc/certbot/certs/live/dev.grav.rocks/fullchain.pem"

Lastly, locate the Server Private Key: section add the location of your SSLCertificateKeyFile you obtained above:

SSLCertificateKeyFile "/usr/local/etc/certbot/certs/live/dev.grav.rocks/privkey.pem"

Save the file at this point. Then all you need to do now is double check your Apache configuration syntax:

sudo apachectl configtest

If all goes well, restart Apache:

brew services stop httpd
brew services start httpd

You can tail -f /usr/local/var/log/httpd/error_log, the Apache error log while you restart to see if you have any errors.

Now simply point your browser at https://dev.grav.rocks and you should see the page loading and a comforting message in the browser address bar about how secure your site is.

Maintenance & Renewal

By their nature LetsEncrypt certificates are short-lived that are valid for 90 days only. This means that you need to renew them. The simplest way to accomplish this is to simply run:

certbot renew
brew services restart httpd

However, you can only run this when you are close to renewal (within 30 days). The best solution is to automate this process by using a cron-job to run the process weekly. First we need to add an entry to the crontab that will run the renewal script every sunday at 3am::

(crontab -l 2>/dev/null; echo "0 3 * * 0 certbot renew --post-hook 'brew services restart httpd' > /dev/null 2>&1") | crontab -

You can check this looks correct by running:

crontab -l

You should see:

0 3 * * 0 certbot renew --post-hook 'brew services restart httpd' > /dev/null 2>&1

Self-Signed SSL

Sometimes there are scenarios where you want to mimic an existing site during development, or you don't have a spare domain name but just need to test under SSL. Either way there are still occasions where a valid SSL certificate is not required, and you just need to get a self-signed SSL certificate in place.

We'll install mkcert to serve as our certificate authority (CA), and also nss to ensure firefox can use certificate authority sever.

brew install mkcert nss

Next we have to install the server and run it (enter your password when prompted):

mkcert -install

Let's create a good location for the certificates:

cd /usr/local/etc/httpd
mkdir certs && cd certs

Now all we have to do is generate a certificate for any domain we wish to use. For example, you could create one for localhost with:

mkcert localhost

or one for grav-admin.test with:

mkcert grav-admin.test

These commands will create .pem and -key.pem files for each domain.

Setting up Apache for Self-Signed SSL certificate

Follow exactly the same steps as the Apache SSL Configuration section above to enable SSL in Apache.

When you get to the part where you set the SSLCertificateFile and SSLCertificateKeyFile use the files we just generated rather than the LetsEncrypt .pem files:

SSLCertificateFile "/usr/local/etc/httpd/certs/grav-admin.test.pem"
SSLCertificateKeyFile "/usr/local/etc/httpd/certs/grav-admin.test-key.pem"

Check the syntax:

/usr/local/bin/httpd -t

Restart the server to have the configuration changes take effect:

brew services restart httpd

SSL in Apache Virtual Hosts

The configuration we've outlined in this article focuses on setting a root-level SSL certificate. You can of course generate multiple unique certificates for various domains and sites. When you do this, you need to enable SSL for a virtual host via editing the httpd-vhosts.conf file.

If you already have a vhost entry for http on port 80 for a particular site, you can just copy this and replace 80 with 443 then add the entries for your key and certificate.

Let's assume we have created a self-signed certificate for grav-admin.test, and we already have a working vhost configuration for http/port 80 that we setup in Part 2 of the guide:

<VirtualHost *:80>
    DocumentRoot "/Users/your_user/Sites/grav-admin"
    ServerName grav-admin.test
</VirtualHost>

Then we simply copy and paste this and change the port to 443 and add references to turn on SSL and use the generated SSL files:

<VirtualHost *:80>
    DocumentRoot "/Users/your_user/Sites/grav-admin"
    ServerName grav-admin.test
</VirtualHost>

<VirtualHost *:443>
    DocumentRoot "/Users/your_user/Sites/grav-admin"
    ServerName grav-admin.test
    SSLEngine on
    SSLCertificateFile "/usr/local/etc/httpd/certs/grav-admin.test.pem"
    SSLCertificateKeyFile "/usr/local/etc/httpd/certs/grav-admin.test-key.pem"
</VirtualHost>

Check the syntax:

/usr/local/bin/httpd -t

Restart the server to have the configuration changes take effect:

brew services restart httpd