How to Generate a Self-Signed SSL Certificate for Apache

How to generate a self-signed SSL certificate for Apache

How to generate a self-signed SSL certificate for Apache

If you’ve ever needed to test HTTPS on a local development box or an internal server that doesn’t need a certificate from a public authority, you’ve probably run into the term “self-signed SSL certificate.” I’ve used these countless times over the years, and in this post I’ll walk you through exactly how I generate and configure one for Apache, along with the pitfalls I’ve learned to avoid.

What Is a Self-Signed SSL Certificate?

A self-signed certificate is exactly what it sounds like — a certificate that I create and sign myself, rather than having it signed by a trusted Certificate Authority (CA) like Let’s Encrypt, DigiCert, or Sectigo. Browsers don’t inherently trust these certificates, which is why you’ll see a warning like “Your connection is not private” when you visit a site using one. But that doesn’t mean they’re useless. Far from it.

Why Would I Use a Self-Signed Certificate?

I reach for self-signed certificates in a handful of situations:

What I don’t do is use self-signed certificates on public-facing production sites. For that, I always recommend Let’s Encrypt, which is free and trusted by every major browser.

Prerequisites

Before you start, make sure you have:

Step 1: Install OpenSSL (If Needed)

On Debian/Ubuntu:

sudo apt update
sudo apt install openssl -y

On CentOS/RHEL:

sudo yum install openssl -y

Step 2: Create a Directory for Your Certificates

I like to keep things organized, so I create a dedicated directory:

sudo mkdir -p /etc/apache2/ssl

On RHEL-based systems, this is typically /etc/httpd/ssl.

Step 3: Generate the Private Key and Certificate

Here’s the command I use most often — it generates a 2048-bit RSA private key and a self-signed certificate valid for 365 days, all in one step:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/apache2/ssl/apache-selfsigned.key \
  -out /etc/apache2/ssl/apache-selfsigned.crt

Let me break down what each flag does:

You’ll be prompted to fill in some details:

Country Name (2 letter code) [XX]: US
State or Province Name: California
Locality Name: San Francisco
Organization Name: My Test Company
Organizational Unit Name: IT Department
Common Name (e.g. server FQDN): mydomain.local
Email Address: admin@mydomain.local

The Common Name field matters most — it should match the domain or hostname you’ll be accessing the server through. If it doesn’t match, browsers will complain even more loudly than they already do about self-signed certs.

Step 4: Generate Strong Diffie-Hellman Parameters (Optional but Recommended)

To improve forward secrecy, I generate a DH parameters file:

sudo openssl dhparam -out /etc/apache2/ssl/dhparam.pem 2048

This can take a minute or two depending on your server’s CPU.

Step 5: Configure Apache to Use the Certificate

Now I need to tell Apache where to find these files. First, make sure the SSL module is enabled:

sudo a2enmod ssl
sudo a2enmod headers

Next, edit (or create) your SSL virtual host configuration, typically at /etc/apache2/sites-available/default-ssl.conf:

<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName mydomain.local
        DocumentRoot /var/www/html

        SSLEngine on
        SSLCertificateFile /etc/apache2/ssl/apache-selfsigned.crt
        SSLCertificateKeyFile /etc/apache2/ssl/apache-selfsigned.key
        SSLOpenSSLConfCmd DHParameters /etc/apache2/ssl/dhparam.pem

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
            SSLOptions +StdEnvVars
        </FilesMatch>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    </VirtualHost>
</IfModule>

On RHEL-based systems, the equivalent config lives in /etc/httpd/conf.d/ssl.conf.

Step 6: Enable the Site and Restart Apache

sudo a2ensite default-ssl.conf
sudo apache2ctl configtest
sudo systemctl restart apache2

If apache2ctl configtest returns Syntax OK, you’re good to go. On CentOS/RHEL, use httpd -t and systemctl restart httpd.

Step 7: Verify the Certificate Is Working

Visit https://mydomain.local in your browser. You should see the “Not Secure” or “Your connection is not private” warning — that’s expected and normal for self-signed certificates. Click “Advanced” and “Proceed” to confirm it loads.

You can also verify from the command line:

openssl s_client -connect mydomain.local:443 -servername mydomain.local

This shows you the certificate chain, expiration date, and cipher suite in use.

Redirecting HTTP to HTTPS

I almost always want to force traffic to HTTPS. I do this by adding a redirect in the port 80 virtual host:

<VirtualHost *:80>
    ServerName mydomain.local
    Redirect permanent / https://mydomain.local/
</VirtualHost>

Common Mistakes I’ve Made (So You Don’t Have To)

Security Best Practices

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5

Performance Optimization Tips

SSLSessionCache shmcb:/var/run/apache2/ssl_scache(512000)
SSLSessionCacheTimeout 300
sudo a2enmod http2

Then add Protocols h2 http/1.1 inside your VirtualHost block.

Troubleshooting Common Issues

Apache won’t start after enabling SSL — check sudo apache2ctl configtest for syntax errors, and confirm file paths to your .crt and .key files are correct.

“SSL certificate problem: self-signed certificate” when using curl — this is expected; use curl -k to bypass verification for testing.

Browser shows ERR_SSL_PROTOCOL_ERROR — usually means Apache isn’t actually listening on port 443, or the SSL module isn’t loaded. Check with sudo apache2ctl -M | grep ssl.

Automating Renewal with a Script

Since self-signed certificates expire (typically after the 365 days I set), I like to avoid the annoyance of forgetting to renew them. I wrote a small script that regenerates the certificate automatically before it expires:

#!/bin/bash
CERT_DIR="/etc/apache2/ssl"
DOMAIN="mydomain.local"

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout "$CERT_DIR/apache-selfsigned.key" \
  -out "$CERT_DIR/apache-selfsigned.crt" \
  -subj "/C=US/ST=California/L=San Francisco/O=My Test Company/CN=$DOMAIN"

systemctl reload apache2

The -subj flag lets me skip the interactive prompts entirely, which is essential if I want to run this unattended via cron. I schedule it a few days before expiration:

sudo crontab -e
0 3 1 1 * /usr/local/bin/renew-selfsigned-cert.sh

This example runs once a year on January 1st at 3 AM, well ahead of a 365-day certificate’s expiration if it was generated in the days following.

Multi-Domain (SAN) Self-Signed Certificates

Modern browsers have deprecated the old practice of relying solely on the Common Name field and instead check the Subject Alternative Name (SAN) extension. If I’m testing multiple subdomains or hostnames, I generate a certificate with SANs using a config file:

cat > san.cnf << 'EOF'
[req]

default_bits = 2048 distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no

[req_distinguished_name]

CN = mydomain.local

[v3_req]

subjectAltName = @alt_names

[alt_names]

DNS.1 = mydomain.local DNS.2 = www.mydomain.local DNS.3 = api.mydomain.local EOF sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/apache2/ssl/apache-selfsigned.key \ -out /etc/apache2/ssl/apache-selfsigned.crt \ -config san.cnf

This is one of the most common gotchas I see — people generate a certificate for one hostname, then wonder why the browser still complains even after accepting the warning once, simply because they’re accessing it via a different subdomain not covered by the certificate.

FAQs

Can I use a self-signed certificate for a production website? Technically yes, but I don’t recommend it. Visitors will see security warnings, which hurts trust and SEO. Use Let’s Encrypt instead for free, trusted certificates.

How long should my self-signed certificate be valid? I usually set 365 days, but for internal tools you rarely revisit, you can extend it to a few years by increasing the -days value.

Do self-signed certificates encrypt traffic? Yes. Encryption works exactly the same as with a CA-signed certificate — the only difference is trust validation.

Can I convert a self-signed certificate to a CA-signed one later? Not directly, but you can generate a CSR from the same key and submit it to a CA when you’re ready to go to production.

Summary and Key Takeaways

Generating a self-signed SSL certificate for Apache is a quick, five-minute task once you know the steps: install OpenSSL, generate the key and certificate, configure Apache’s virtual host, and restart the service. I use this approach constantly for local development and internal tools where public trust isn’t a requirement. Just remember to switch to a properly signed certificate before going live to the public.

References

Exit mobile version