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
localhostor a.testdomain. - 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 (
apache2on Debian-based systems,httpdon 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/tcporsudo 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
SSLSessionCacheto 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.