Part 6 – Setting Up HTTPS

November 29, 2024

!Series: Local Environment Apache OpenSSL

In this section, we will be setting up our site to accept connections over HTTPS.

General Concepts

In order to understand the rest of this section, we need to think about a few fundamentals. Specifically, how does the internet work and how can we transmit data (reasonably) securely over it?

All the data available on the internet, from the simplest personal homepage to the most popular apps in the world, are simply pieces of information stored on a computer somewhere.

That means that the internet is simply a series of wires and cables connected by routers which direct pieces of information to and from their destination. HTTP is a protocol that acts like a common language which all the machines connected to the internet are able to use to exchange data.

However, there’s a problem with HTTP. HTTP sends everything in plain text. That’s fine if it’s information that can be safely shown to anyone, but it’s a problem if you want to do something like entering a password or transmitting your credit card details.

Early in the history of the internet, it became apparent that we needed a way to add security to HTTP requests. This led to the development of HTTPS, which stands for Hypertext Transfer Protocol Secure. HTTPS still uses the same protocol to exchange information, but it uses extra technology to do that more securely. Specifically, it uses SSL or TLS, with TLS being the more modern version.

SSL/TLS has three goals:

  • Make sure data can’t be read by unauthorized parties (confidentiality)
  • Make sure data can’t be tampered with on the way to its destination (integrity)
  • Make sure the party sending the information is who they say they are (authentication)

This is a deep and interesting rabbit hole and I would recommend looking at the resources in Further Reading for more information. In the interests of not just paraphrasing other people’s existing work, I’m not going to get further into the details here.

For our purposes, we’re going to need some more components to allow our site to use HTTPS. First, we will need a public / private key set that can secure our data and make sure it can’t be tampered with (confidentiality and integrity). And second, we will need to create a certificate that validates the identity of our local website (authentication).

Installing OpenSSL

On a basic hosting package, your hosting provider will probably set up HTTPS for you. (If you’re self-hosting or using a more complex setup, this may not be true—but you also probably aren’t reading this tutorial in that case.) However, we need to do more legwork in a local environment.

We’ll be using OpenSSL to generate our keys and a certificate. To get OpenSSL, download it at this link. You will want the Windows Installer for the most recent version.

Screenshot of browser

OpenSSL Windows Installer download

Run the installer. Proceed through the installation screens. When you have the option to do so, check “Adjust PATH system environment”.

Screenshot of setup screen

Adding OpenSSL to PATH during setup

After installation is completed, run Command Prompt as an administrator. Test whether OpenSSL has installed correctly by running the command:

openssl

If OpenSSL installed correctly, you should see a list of commands.

Screenshot of Command Prompt

OpenSSL list of commands (appears when entering openssl without any additional commands)

Creating a Certificate Authority

On a live website, a certificate would be signed by a trusted Certificate Authority, such as Let’s Encrypt, GlobalSign, or IdenTrust. You can see what certificate authority signed a website’s certificate by looking at the security information in your browser.

Screenshot of browser

Site certificate information as it appears in Firefox

Since that isn’t possible for a local site, we are going to create our own Certificate Authority and tell our computer to trust it.

Create a new folder called CA in the Development folder.

Screenshot of File Explorer

Development folder with CA folder

In Command Prompt, CD into that folder.

cd C:\Development\CA

Enter the following command:

openssl req -x509 -noenc -newkey RSA:2048 -keyout root-ca.key -days 365 -out root-ca.crt -subj "/CN=Local Root"

Let’s walk through what each of the parts of this command are doing:

  • openssl indicates the command is an OpenSSL command
  • req indicates that we are making a certificate request
  • -x509 indicates a few things:
    • We want a certificate and not a certificate signing request
    • We want the certificate in the X.509 format (which will include both information about who issued the certificate and a public key)
  • -noenc indicates that the private key shouldn’t be encrypted
  • -newkey indicates we want a private key created
  • RSA:2048 indicates we want the private key to be an RSA key 2048 bits in size
  • -keyout root-ca.key indicates the private key file should be given the name root-ca.key
  • -days 365 indicates that the certificate will be good for 365 days
  • -out root-ca.crt indicates that the certificate will be saved as a file called root-ca.crt
  • -subj "/CN=Local Root" adds information about the certificate and specifies a Common Name
    • You need to specify Common Name for other parts of this process to work correctly

If the process completed correctly, you shouldn’t see any errors in the command prompt panel and you should see two files created in the CA folder, one called root-ca.crt and one called root-ca.key.

Screenshot of File Explorer

CA folder with root certificate and private key

Double-click the certificate file to open it. You will see that the certificate says that it’s not a trusted root certificate yet. We’ll need to fix that.

Screenshot of certificate details

Certificate Information (not trusted)

Trusting the Root CA

Since our certificate doesn’t come from a generally trusted Certificate Authority, we need to take some extra steps to make it trusted on our local machine. Chromium-based browsers (such as Chrome and Edge) use the Windows Certificate Store to determine if the certificate should be trusted,[1] whereas Firefox maintains its own records.

Adding the Root CA to the Windows Certificate Store

Type mmc into the search bar and click “Run command” or launch Run and use the command mmc.

Screenshot of Start Menu (search bar)

Running MMC from the search bar

This launches the Microsoft Management Console.

Screenshot of Microsoft Management Console

MMC on launch

Under File, click “Add/Remove Snap-in”.

Screenshot of Microsoft Management Console

MMC File menu

Select “Certificates”, click “Add”, and then “OK”.

Screenshot of Microsoft Management Console

Add Certificates Snap-in

Choose “Computer account” and click “Next”.

Screenshot of Microsoft Management Console

Certificates snap-in scope screen

Choose “Local computer” and click “Finish”. Click “OK” on the Add or Remove Snap-ins screen.

Screenshot of Microsoft Management Console

Certificates snap-in computer screen

The console should now show “Certificates” in the leftmost panel. Click on it to see the Certificates logical stores in the center panel. In the center panel, click on “Trusted Root Certification Authorities” to show the Trusted Root Certification Authorities actions in the right panel.

Screenshot of Microsoft Management Console

MMC console with Trusted Root Certification Authorities actions

Under “Trusted Root Certification Authorities” on the right, click on “More Actions”, choose “All Tasks”, and then “Import”.

Screenshot of Microsoft Management Console

Import certificate in MMC console

The Certificate Import Wizard will launch. Click “Next” and then locate the certificate. You want to import the certificate file (root-ca.crt), not the private key.

Screenshot of Certificate Import Wizard

Root certificate selected in Certificate Import Wizard

Accept the default, which is to place the certificate in Trusted Root Certification Authorities. Click “Finish”. You should get a popup that says that import was successful.

If you click into Trusted Root Certification Authorities and then into Certificates, you should be able to see your new certificate listed.

Screenshot of Microsoft Management Console

Local Root in the list of Trusted Root Certification Authorities

If you double click on it now, you’ll be able to see that the certificate is now trusted for all purposes.

Screenshot of certificate details

Certificate Information (fully trusted)

We only want to use the certificate for specific purposes, so let’s go into Details and Edit Properties to change that.

Screenshot of certificate details

Edit Properties in the certificate window

Choose “Enable only the following purposes” and uncheck everything except Server Authentication. (You will have to uncheck everything one at a time as there does not appear to be a uncheck all option.)

Screenshot of certificate details

Enabling only certain purposes in the Certificate window

Click “Apply” and then “OK”. Close out of MMC.

Adding the Root CA to Firefox

In Firefox, go to about:preferences#privacy. Scroll down to Certificates and click on “View Certificates”.

Screenshot of browser

Firefox certificate settings

Click on “Import”.

Screenshot of browser

Importing certificates in Firefox Certificate Manager

Select the root-ca.crt file from C:\Development\CA and click “Open”. Choose “Trust this CA to identify websites” and click OK.

Screenshot of browser

Choose CA purposes in Firefox Certificate Manager

If you scroll down in the list of certificates, you should see your newly added certificate. Click “OK” to close.

Screenshot of browser

Local Root Certificate Authority in the list of Firefox CAs

Creating Site Private Keys and Certificates

We need to create a private key and certificate for each of our websites, using our Certificate Authority as the certificate issuer. To do that, we’ll create a private key and a Certificate Signing Request (CSR), and then use our root Certificate Authority to generate a certificate.

Creating a Private Key and CSR

Open Command Prompt in as an administrator. For now, I’m still doing this in C:\Development\CA. Run the following command:

openssl req -noenc -newkey rsa:2048 -keyout test-1.key -out test-1.csr -subj "/CN=test-1.local"

Let’s walk through what each of the parts of this command are doing:

  • openssl indicates the command is an OpenSSL command
  • req indicates that we are making a certificate request
    • Because we didn’t put -x509, this will generate a certificate signing request instead of a certificate
  • -noenc indicates that the private key shouldn’t be encrypted
  • -newkey indicates we want a private key created
  • RSA:2048 indicates we want the private key to be an RSA key 2048 bits in size
  • -keyout test-1.key indicates the private key file should be given the name test-1.key
  • -out test-1.csr indicates that the certificate signing request should be saved as a file called test-1.csr
  • -subj "/CN=test-1.local" adds information about the certificate and specifies a Common Name

Repeat the process for test-2 with the following command:

openssl req -noenc -newkey rsa:2048 -keyout test-2.key -out test-2.csr -subj "/CN=test-2.local"

When both commands have been run, you should have four new files:

  • test-1.key, the private key for test-1.local
  • test-1.csr, a certificate signing request for test-1.local
  • test-2.key, the private key for test-2.local
  • test-2.csr, a certificate signing request for test-2.local

Screenshot of File Explorer

CA folder with private keys and CSRs
Generating Certificates

We need to use the two Certificate Signing Requests to create our site certificates. Before we complete that process, we also need to create a couple of option files to give additional details about generating our certificates. Create the following files:

test-1-opt.txt:

subjectAltName = DNS:test-1.local
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth

test-2-opt.txt:

subjectAltName = DNS:test-2.local
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth

Once again, let’s walk through what these commands are doing:

  • subjectAltName specifies a Subject Name (which is required in may circumstances)
    • If we were supporting multiple ways to reach our sites (e.g. www.test-1.local), we could have multiple lines here
  • authorityKeyIdentifier indicates what kind of info should be carried over from the Certificate Authority
  • basicConstraints = CA:FALSE indicates that the certificate we’re creating is not a Certificate Authority
  • keyUsage indicates how the public key can be used (in this case, to create digital keys and I think to aid in key exchange)
  • extendedKeySusage indicates more ways the public key can be used—in this case to perform SSL/TLS server authentication

With that done, we’re ready to create our certificates. Use the following command:

openssl x509 -req -CA root-ca.crt -CAkey root-ca.key -in test-1.csr -out test-1.crt -days 365 -extfile test-1-opt.txt

This looks very similar to what we did when we created the CA certificate earlier, but it’s actually a different command. Earlier, we were using req as the command and -x509 as the option, but now we’re using x509 as the command and -req as the option. Let’s walk through it:

  • x509 is a general command that lets you do a bunch of things with certificates
  • -req indicates that the command should expect a CSR instead of a finished certificate
  • -CA root-ca.crt indicates that root-ca.crt should be used as the Certificate Authority to sign the certificate
  • -CAkey root-ca.key indicates that the private key for the root certificate is a file named root-ca.key
  • -in test-1.csr indicates that the Certificate Signing Request is a file named test-1.csr
  • -out test-1.crt indicates that the output file should be called test-1.crt
  • -days 365 indicates that the certificate will be good for 365 days
  • -extfile test-1-opt.txt indicates that the certificate should use the options specified in test-1.opt.txt

Run the command. You should get a message that confirms the certificate was created.

Screenshot of Command Prompt

Certificate created confirmation in Command Prompt

Repeat the process with this command:

openssl x509 -req -CA root-ca.crt -CAkey root-ca.key -in test-2.csr -out test-2.crt -days 365 -extfile test-2-opt.txt

You should see newly created certificates in the CA folder. Double-click on one of them to see more details. You should see that the certificate is issued to your site but issued by the Common Name of the certificate authority we created earlier.

Screenshot of certificate details

Certificate details for test-1.local site certificate

With that done, all of our certificates and keys are ready! You can delete the two CSR files and the option files.

Installing Certificates

Now that we’ve created our site certificates and private keys, we just need to set up our sites to use them. Let’s create a home for those files at C:\Development\www\test-1\ssl and C:\Development\www\test-2\ssl.

Move the certificate and key files into the SSL directory. For example, we should end up with test-1.crt and test-1.key in C:\Development\www\test-1\ssl.

Screenshot of File Explorer

Certificate and key in ssl folder

Open the Apache configuration directory and open the main Apache configuration file (C:\Development\Apache24\conf\httpd.conf). We will use this file to update some global settings. Uncomment the following lines:

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf

Next, we need to make some changes in httpd-ssl.conf (located in C:\Development\Apache24\conf\extra). Most of the settings are fine as is, but we’re going to be handling our virtual hosts settings in our httpd-vhosts.conf file, so we need to comment out everything within the <VirtualHost> statement. (At the time of writing, that’s from Line 121 to the end of the file.)

Finally, open httpd-vhosts.conf (located in C:\Development\Apache24\conf\extra). Change the ports for our sites to :443. You’ll also need to add information in each Virtual Host directive to tell Apache where to find the certificate and private key files.

Inside the VirtualHost for test-1.local:

SSLEngine on
SSLCertificateFile "${DOCROOT}/test-1/ssl/test-1.crt"
SSLCertificateKeyFile "${DOCROOT}/test-1/ssl/test-1.key"

Inside the VirtualHost for test-2.local:

SSLEngine on
SSLCertificateFile "${DOCROOT}/test-2/ssl/test-2.crt"
SSLCertificateKeyFile "${DOCROOT}/test-2/ssl/test-2.key"

If you restart Apache and try visiting https://test-1.local, you should see that the site works. Moreover, you should see something that indicates you’ve connected via HTTPS.

Screenshot of browser

Site certificate information for test-1 with HTTPS

There’s one last issue, though. If you try visiting http://test-1.local, you just get the “It works!” screen. That’s because we’ve set up that page as a fallback for any requests received on Port 80 (see Part 3 for a refresher). We can fix that by adding a couple more virtual hosts to handle that situation:

<VirtualHost *:80>
    ServerName test-1.local
    Redirect permanent "/" "https://test-1.local/"
</VirtualHost>

<VirtualHost *:80>
    ServerName test-2.local
    Redirect permanent "/" "https://test-2.local/"
</VirtualHost>

This will just listen on Port 80 (the default port for HTTP requests) for any requests for test-1.local or test-2 local. If it gets any, it will redirect to the HTTPS version of the site, and it will send a 301 response code (meaning the requested site has been permanently moved).

With that done, our final httpd-vhosts.conf code looks like this:

# Fallback if no other conditions met
<VirtualHost *:80>
    ServerName localhost
</VirtualHost>

# Permanent redirect from HTTP to HTTPS
<VirtualHost *:80>
    ServerName test-1.local
    Redirect permanent "/" "https://test-1.local/"
</VirtualHost>

<VirtualHost *:80>
    ServerName test-2.local
    Redirect permanent "/" "https://test-2.local/"
</VirtualHost>

# Main virtual hosts instructions
<VirtualHost *:443>
    ServerName test-1.local
    DocumentRoot "${DOCROOT}/test-1"
    
    <Directory "${DOCROOT}/test-1/">
        Require all granted
    </Directory>
    
    <IfModule fcgid_module>
        FcgidInitialEnv PATH "${PHPROOT}/php-8.3.9"
        FcgidInitialEnv PHPRC "${PHPROOT}/php-8.3.9"
        <Files ~ "\.php$>"
            Options ExecCGI
            AddHandler fcgid-script .php
            FcgidWrapper "${PHPROOT}/php-8.3.9/php-cgi.exe" .php
        </Files>
    </IfModule>
    
    SSLEngine on
    SSLCertificateFile "${DOCROOT}/test-1/ssl/test-1.crt"
    SSLCertificateKeyFile "${DOCROOT}/test-1/ssl/test-1.key"
    
</VirtualHost>

<VirtualHost *:443>
    ServerName test-2.local
    DocumentRoot "${DOCROOT}/test-2"
    
    <Directory "${DOCROOT}/test-2/">
        Require all granted
    </Directory>
    
    <IfModule fcgid_module>
        FcgidInitialEnv PATH "${PHPROOT}/php-7.4.9"
        FcgidInitialEnv PHPRC "${PHPROOT}/php-7.4.9"
        <Files ~ "\.php$>"
            Options ExecCGI
            AddHandler fcgid-script .php
            FcgidWrapper "${PHPROOT}/php-7.4.9/php-cgi.exe" .php
        </Files>
    </IfModule>
    
    SSLEngine on
    SSLCertificateFile "${DOCROOT}/test-2/ssl/test-2.crt"
    SSLCertificateKeyFile "${DOCROOT}/test-2/ssl/test-2.key"

</VirtualHost>

Conclusion

That’s it for this section. In the next (and final) section, we will install WordPress on one of our sites.

Further Reading

Note: Practical Networking has a series of especially good videos that are part of a larger paid course (which I haven’t taken and can’t speak to). I have highlighted the videos I personally found especially useful here.

Notes

[1] Internet Explorer isn’t Chromium-based, but also uses the Windows Certificate store.

Posted In: Tutorials