What this guide covers
Apache HTTP Server is the world's most widely deployed web server, powering millions of Linux hosts. This guide walks through installing a commercially issued SSL/TLS certificate on two major Linux families:
- Ubuntu 22.04 / 24.04 and Debian 11/12 — which use the
apache2package and thea2enmod/a2ensitetooling - Rocky Linux 8/9 and AlmaLinux 8/9 — the recommended CentOS replacements, which use
httpdandfirewalld
CentOS users: CentOS 7 reached end-of-life in June 2024 and CentOS 8 in December 2021. Neither receives security updates. Migrate to Rocky Linux or AlmaLinux — this guide's RHEL/Rocky section applies to both.
If you haven't ordered your certificate yet, see My-SSL's SSL certificate options. DV certificates are typically issued within minutes; OV and EV take one to three business days. Have your issued .crt file and CA bundle ready before starting this guide.
Prerequisites
- Root or sudo access on the server
- Apache 2.4.x installed and running (apache2 on Ubuntu/Debian, httpd on RHEL-family)
- A domain name with a live A record pointing to the server's public IP
- An SSL certificate issued by your CA — the .crt file plus the CA bundle
- The private key (.key) generated when you created your CSR — keep this file secret
- TCP port 443 open in your firewall (ufw on Ubuntu, firewalld on Rocky)
Haven't generated a CSR yet? Our CSR generation guide walks through it step by step, or use the online CSR Generator to produce a CSR and matching private key in your browser.
Certificate files you need
Your CA delivers a zip archive. The three files Apache needs are:
| File | Apache directive | Notes |
|---|---|---|
| your_domain.crt | SSLCertificateFile | Your server certificate |
| your_domain.key | SSLCertificateKeyFile | Private key — never share this |
| ca_bundle.crt | SSLCertificateFile (combined) | Intermediate + root chain from your CA |
Apache 2.4.8+ (all modern distros): Instead of the deprecated SSLCertificateChainFile directive, combine your certificate and CA bundle into a single fullchain file and point SSLCertificateFile at it. This eliminates the "incomplete chain" browser warning.
# Create a combined fullchain file (server cert first, then CA bundle) cat your_domain.crt ca_bundle.crt > your_domain_fullchain.crt
Install Apache and mod_ssl — Ubuntu / Debian
If Apache is already installed and running, skip to .
Step 1 — Install apache2 and enable SSL modules
sudo apt update sudo apt install -y apache2 sudo a2enmod ssl sudo a2enmod headers sudo a2enmod rewrite sudo systemctl enable apache2 sudo systemctl start apache2
Step 2 — Open port 443 in UFW
sudo ufw allow 'Apache Full' sudo ufw reload sudo ufw status
"Apache Full" opens both port 80 (HTTP) and port 443 (HTTPS). If you use a different firewall or a cloud security group, open TCP 443 there instead.
Install httpd and mod_ssl — Rocky Linux / AlmaLinux
Step 1 — Install httpd and mod_ssl
sudo dnf install -y httpd mod_ssl sudo systemctl enable --now httpd
Installing mod_ssl drops a default TLS configuration at /etc/httpd/conf.d/ssl.conf. You will override the relevant parts with your own virtual host file.
Step 2 — Open port 443 in firewalld
sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload sudo firewall-cmd --list-services
Upload your certificate files to the server
Copy the fullchain file and private key to the server using SCP, SFTP, or your deployment tool. The conventional paths differ by distro:
Ubuntu / Debian
# Certificates sudo cp your_domain_fullchain.crt \ /etc/ssl/certs/ # Private key sudo cp your_domain.key \ /etc/ssl/private/ sudo chmod 600 \ /etc/ssl/private/your_domain.key
Rocky Linux / AlmaLinux
# Certificates sudo cp your_domain_fullchain.crt \ /etc/pki/tls/certs/ # Private key sudo cp your_domain.key \ /etc/pki/tls/private/ sudo chmod 600 \ /etc/pki/tls/private/your_domain.key
Protect the private key. Set permissions to 600 (readable only by root). Never store it in a web-accessible directory or commit it to version control. If the key is ever exposed, revoke and reissue the certificate immediately.
Configure the SSL virtual host — Ubuntu / Debian
Create a dedicated virtual host file for HTTPS. Replace yourdomain.com and the file paths throughout:
sudo nano /etc/apache2/sites-available/yourdomain.com-ssl.conf
Paste this configuration:
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain.com/public_html
# TLS configuration
SSLEngine on
SSLCertificateFile /etc/ssl/certs/your_domain_fullchain.crt
SSLCertificateKeyFile /etc/ssl/private/your_domain.key
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-ssl-access.log combined
</VirtualHost>Enable the site:
sudo a2ensite yourdomain.com-ssl.conf
Configure the SSL virtual host — Rocky Linux / AlmaLinux
Create a new config file in /etc/httpd/conf.d/. Files in that directory are auto-included by httpd:
sudo nano /etc/httpd/conf.d/yourdomain.com-ssl.conf
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain.com/public_html
# TLS configuration
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/your_domain_fullchain.crt
SSLCertificateKeyFile /etc/pki/tls/private/your_domain.key
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
ErrorLog /var/log/httpd/yourdomain.com-ssl-error.log
CustomLog /var/log/httpd/yourdomain.com-ssl-access.log combined
</VirtualHost>If the default /etc/httpd/conf.d/ssl.conf already contains a <VirtualHost *:443> block that conflicts, comment it out or delete it — only one virtual host should listen on port 443 per ServerName.
Force HTTP → HTTPS
Redirect all plain-HTTP traffic permanently to HTTPS with a port-80 virtual host.
Ubuntu / Debian — add to /etc/apache2/sites-available/yourdomain.com.conf:
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
# Permanent 301 redirect to HTTPS
Redirect permanent / https://yourdomain.com/
</VirtualHost># Enable the HTTP site and disable the default placeholder sudo a2ensite yourdomain.com.conf sudo a2dissite 000-default.conf
Rocky Linux / AlmaLinux — create or add to /etc/httpd/conf.d/yourdomain.com.conf:
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>Harden TLS: protocols and cipher suite
Add these directives inside your <VirtualHost *:443> block or globally in the mod_ssl config file:
# Allow TLS 1.2 and 1.3 only — disable SSLv3, TLS 1.0, TLS 1.1 SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 # Prefer server cipher order for TLS 1.2 SSLHonorCipherOrder on # Strong cipher suite (TLS 1.3 uses its own built-in negotiation) SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 # Disable TLS session tickets for better forward secrecy SSLSessionTickets off
For an always-current, distro-aware configuration, use the Mozilla SSL Configuration Generator (select Apache and "Intermediate" for broad compatibility, or "Modern" if you don't need to support older browsers).
Test the configuration and reload Apache
Always test before reloading — a syntax error causes the reload to fail, and your site can go down if Apache was not already running with a valid config.
Ubuntu / Debian
sudo apache2ctl configtest # Expected: Syntax OK sudo systemctl reload apache2
Rocky Linux / AlmaLinux
sudo apachectl configtest # Expected: Syntax OK sudo systemctl reload httpd
Verify the SSL installation
Three ways to confirm everything is working:
1. Browser check
Open https://yourdomain.com in a browser. The padlock should appear with no warnings. Click the padlock to confirm the issuer, Subject, and validity dates match what your CA issued.
2. OpenSSL command line
openssl s_client -connect yourdomain.com:443 -showcerts # Look for: # Verify return code: 0 (ok) # This means the full chain is present and the certificate is trusted.
3. Online SSL Checker
Use the My-SSL SSL Checker to verify chain completeness, expiry date, protocol support, and mixed-content status — all from an external vantage point.
Automate renewals — critical with 199-day certificate lifetimes
2026 change: The CA/Browser Forum reduced the maximum TLS certificate validity period to 199 days, effective March 15, 2026 (Ballot SC-081). The limit drops to 100 days in 2027 and 47 days by 2029. At 47 days you'll need approximately 8 renewals per year per domain — manual renewal is no longer practical.
For commercial certificates purchased from a CA, automate the install step with a shell script triggered by cron:
- Set a calendar alert 30 days before expiry (your CA or My-SSL will email you)
- Generate a new CSR with a fresh private key
- Submit for reissuance and complete domain validation
- Download the new certificate and CA bundle
- Combine into a fullchain file, copy to the server, and reload Apache
For Let's Encrypt / ACME (free DV certificates), Certbot with the Apache plugin handles the full renewal loop:
# Ubuntu/Debian — install Certbot with Apache plugin sudo apt install -y certbot python3-certbot-apache # Obtain and install a Let's Encrypt certificate automatically sudo certbot --apache -d yourdomain.com -d www.yourdomain.com # Test the renewal process (dry run — no cert issued) sudo certbot renew --dry-run
For the full Certbot setup covering DNS-01 wildcards, Docker, Kubernetes, and renewal hooks, see our Certbot & ACME automation guide. For context on why certificate lifetimes are shrinking, read 2026 SSL certificate lifetime changes. To understand the 199-day milestone specifically, see SSL certificates are now limited to 199 days.
Troubleshooting common errors
AH02572: No SSL Certificate set — not enabling HTTPS for VirtualHost
SSLCertificateFile points to a missing or unreadable file. Run ls -la on the cert path. The file must be readable by the Apache process (www-data on Ubuntu, apache on Rocky). Check that SSLEngine on is present in the VirtualHost block.
NET::ERR_CERT_AUTHORITY_INVALID / certificate verify failed
Your certificate chain is incomplete. The fullchain file must contain the server certificate followed by all intermediate CA certificates. Run: openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt your_domain_fullchain.crt. If it fails, rebuild the file: cat your_domain.crt ca_bundle.crt > your_domain_fullchain.crt
AH00526: Syntax error — SSLCertificateKeyFile does not exist
Verify the exact path to the private key file. Ubuntu/Debian: /etc/ssl/private/. Rocky/AlmaLinux: /etc/pki/tls/private/. Run sudo apachectl configtest to see the full error message.
Port 443 not accessible — connection refused or timeout
Check the firewall: sudo ufw status verbose (Ubuntu) or sudo firewall-cmd --list-all (Rocky). Port 443 / service https must be allowed. Also check that Apache is listening: sudo ss -tlnp | grep 443
Mixed content warnings in the browser
The page loads some resources (images, scripts, fonts) over plain HTTP. Update those URLs to use relative paths or https:// absolute URLs. The browser DevTools console lists the offending resources.