Running an HTTPS endpoint protects credentials, cookies, and API payloads from being read or modified in transit. In internal networks and short-lived development environments, a self-signed certificate provides encryption without waiting on external Certificate Authority issuance. The trade-off is trust: browsers and clients warn unless the certificate (or a private CA) is explicitly trusted.

Apache terminates TLS through mod_ssl on port 443, presenting an X.509 certificate during the handshake and using the corresponding private key to prove ownership. Each HTTPS VirtualHost points to a certificate file (commonly .crt) via SSLCertificateFile and a key file (commonly .key) via SSLCertificateKeyFile. A self-signed certificate signs its own public key, so the chain of trust ends at the server unless the certificate is installed as a trust anchor on clients.

Commands and paths target the Ubuntu and Debian Apache packaging, which uses /etc/apache2/ plus the a2enmod and a2ensite helpers. Modern clients validate hostnames from the subjectAltName extension, so relying on a Common Name alone can trigger certificate errors. Keep the private key readable only by root, and use a publicly trusted certificate for Internet-facing sites.

Steps to set up a self-signed TLS certificate for Apache:

  1. Open a terminal.
  2. Create a directory for the certificate and private key.
    $ sudo mkdir -p /etc/apache2/ssl
  3. Generate a self-signed certificate and private key with OpenSSL using subjectAltName for the hostname.
    $ sudo openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
      -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt \
      -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com"
    Generating a RSA private key
    ................................................+++++
    ................................................+++++
    writing new private key to '/etc/apache2/ssl/apache.key'
    -----

    The --nodes option writes an unencrypted private key, so strict file permissions are required.

    Add more names by extending the value, for example subjectAltName=DNS:example.com,DNS:www.example.com.

  4. Restrict the private key to root-only read access.
    $ sudo chmod 600 /etc/apache2/ssl/apache.key
    $ sudo ls -l /etc/apache2/ssl/apache.key
    -rw------- 1 root root 1704 Dec 13 12:34 /etc/apache2/ssl/apache.key
  5. Enable mod_ssl.
    $ sudo a2enmod ssl
    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

    If HTTPS refuses connections, confirm Listen 443 exists in /etc/apache2/ports.conf/.

  6. Edit /etc/apache2/sites-available/example.com.conf/.
    $ sudo vi /etc/apache2/sites-available/example.com.conf

    Back up existing site configuration files before edits to avoid overwriting a working configuration.

  7. Add an HTTPS VirtualHost on port 443 that references the generated certificate and key.
    <VirtualHost *:443>
      ServerName example.com
     
      SSLEngine on
      SSLCertificateFile /etc/apache2/ssl/apache.crt
      SSLCertificateKeyFile /etc/apache2/ssl/apache.key
    </VirtualHost>
  8. Enable the site configuration.
    $ sudo a2ensite example.com.conf
    Enabling site example.com.
    To activate the new configuration, you need to run:
      systemctl reload apache2
  9. Optionally redirect HTTP traffic to HTTPS in the port 80 VirtualHost.
    <VirtualHost *:80>
      ServerName example.com
      Redirect permanent / https://example.com/
    </VirtualHost>

    A redirect is best placed in the existing port 80 site for the same ServerName.

  10. Test the Apache configuration for syntax errors.
    $ sudo apache2ctl configtest
    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
    Syntax OK

    Suppress the AH00558 warning by setting a global ServerName in /etc/apache2/apache2.conf/.

  11. Restart Apache to load mod_ssl with the updated VirtualHost configuration.
    $ sudo systemctl restart apache2

    On RHEL-based distributions the service is commonly named httpd.

  12. Verify TLS negotiation and certificate presentation over HTTPS.
    $ curl -kv https://example.com/
    *   Trying 203.0.113.10:443...
    * Connected to example.com (203.0.113.10) port 443 (#0)
    * ALPN: offers h2,http/1.1
    ##### snipped #####
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
    * Server certificate:
    *  subject: CN=example.com
    *  issuer: CN=example.com
    *  SSL certificate verify result: self-signed certificate (18), continuing anyway.
    > GET / HTTP/1.1
    > Host: example.com
    ##### snipped #####

    Remove --insecure from curl after the certificate is trusted by the client.

  13. Optionally trust the self-signed certificate on a Debian or Ubuntu client to avoid browser warnings.
    $ sudo cp /etc/apache2/ssl/apache.crt /usr/local/share/ca-certificates/example.com-self-signed.crt
    $ sudo update-ca-certificates
    Updating certificates in /etc/ssl/certs...
    1 added, 0 removed; done.
    Running hooks in /etc/ca-certificates/update.d...
    ##### snipped #####
    done.

    Some applications, including Firefox, use a separate trust store that requires an explicit import.

  14. Verify certificate trust without --insecure after installing the certificate into the trust store.
    $ curl -v https://example.com/
    *   Trying 203.0.113.10:443...
    * Connected to example.com (203.0.113.10) port 443 (#0)
    ##### snipped #####
    * SSL certificate verify ok.
    > GET / HTTP/1.1
    > Host: example.com
    ##### snipped #####
Discuss the article:

Comment anonymously. Login not required.