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:

  • Local development — testing HTTPS-only features (like secure cookies or service workers) on localhost or a .test domain.
  • Internal tools — dashboards, admin panels, or APIs that only my team accesses over a VPN.
  • Staging environments — where I want encryption in transit but don’t need public trust.
  • Learning and experimentation — understanding how TLS handshakes and certificate chains work without touching a real CA.

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:

  • A Linux server (I’ll use Ubuntu/Debian syntax, but I’ll note CentOS/RHEL differences where relevant)
  • Apache installed (apache2 on Debian-based systems, httpd on RHEL-based systems)
  • OpenSSL installed (it usually ships by default, but you can check with openssl version)
  • Root or sudo access

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:

  • req -x509 — tells OpenSSL to create a self-signed certificate instead of a certificate signing request (CSR).
  • -nodes — “no DES,” meaning the private key won’t be encrypted with a passphrase (useful because Apache needs to read it without manual intervention on restart).
  • -days 365 — sets the validity period.
  • -newkey rsa:2048 — generates a new 2048-bit RSA key.
  • -keyout — where the private key is saved.
  • -out — where the certificate is saved.

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)

  • Forgetting to open port 443 in the firewall (sudo ufw allow 443/tcp or sudo firewall-cmd --add-service=https --permanent).
  • Mismatched Common Name — always match it to how you access the server.
  • Not restarting Apache after configuration changes.
  • Leaving the private key world-readable — I always set permissions with sudo chmod 600 /etc/apache2/ssl/apache-selfsigned.key.
  • Using self-signed certs in production for public users, which triggers browser warnings and destroys trust.

Security Best Practices

  • Keep private keys restricted to root (chmod 600).
  • Use at least a 2048-bit key (4096-bit if your CPU can handle the overhead).
  • Disable outdated protocols like SSLv3 and TLS 1.0/1.1 in your config:
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5
  • Rotate certificates periodically, even self-signed ones, to build good habits for when you move to production certs.

Performance Optimization Tips

  • Enable SSLSessionCache to reduce handshake overhead:
SSLSessionCache shmcb:/var/run/apache2/ssl_scache(512000)
SSLSessionCacheTimeout 300
  • Use HTTP/2 alongside TLS for faster page loads:
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

Total
1
Shares

Leave a Reply

Previous Post
How to use logrotate to manage Apache logs

How to Use Logrotate to Manage Apache Logs

Next Post
You can obtain and install a free SSL certificate for your website using Let's Encrypt, a widely recognized certificate authority (CA) that offers free SSL certificates. Let's Encrypt provides an automated way to secure your website with HTTPS. Here's how to do it: **1. Ensure Your Server Meets Requirements:** Before you begin, make sure you have: - A registered domain name pointed to your server's IP address. - Shell access to your server. - Apache or Nginx web server installed and configured. **2. Install Certbot:** Certbot is an official client for Let's Encrypt. Install it on your server based on your operating system: - On Ubuntu/Debian: ```bash sudo apt update sudo apt install certbot python3-certbot-apache ``` - On CentOS/RHEL: ```bash sudo yum install epel-release # Enable the EPEL repository (if not already enabled) sudo yum install certbot python3-certbot-apache ``` **3. Request a Certificate:** Use Certbot to request a certificate for your domain. Replace `your_domain.com` with your actual domain name: For Apache: ```bash sudo certbot --apache -d your_domain.com ``` For Nginx: ```bash sudo certbot --nginx -d your_domain.com ``` Certbot will guide you through the process, asking for your email address and whether you agree to the terms of service. It will also give you the option to redirect all HTTP traffic to HTTPS, which is recommended. **4. Verify Certificate Installation:** Certbot will automatically configure your web server to use the newly obtained SSL certificate. It will also schedule automatic certificate renewals. To verify that your certificate is installed correctly and that HTTPS is working, open your web browser and navigate to your website using `https://` (e.g., https://your_domain.com). You should see a padlock icon indicating a secure connection. **5. Automatic Certificate Renewal:** Let's Encrypt certificates are valid for 90 days, but Certbot will automatically renew them when necessary. You can test the renewal process with the following command: ```bash sudo certbot renew --dry-run ``` **6. Additional Configuration (Optional):** After obtaining the SSL certificate, you may want to configure your web server further for security and performance. Here are a few optional steps: - Enable HSTS (HTTP Strict Transport Security) to ensure secure connections: Add the following to your Apache or Nginx configuration: For Apache: ```apache Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" ``` For Nginx: ```nginx add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; ``` - Implement security best practices, such as disabling unnecessary server signatures and configuring security headers. - Set up a content security policy (CSP) to protect against cross-site scripting (XSS) attacks. By following these steps, you can obtain and install a free SSL certificate from Let's Encrypt to secure your website with HTTPS. This helps protect the privacy and security of your users and improves your website's trustworthiness.

How to obtain and install a free SSL certificate with Let’s Encrypt

Related Posts