Many Apache administrators encounter this peculiar situation where SSL certificates appear to work perfectly (green padlock, SSL Labs A+ rating), yet configuration tests fail with file not found errors. Let's dissect this behavior.
# Typical error message seen:
SSLCertificateFile: file '/etc/letsencrypt/live/example.com/fullchain.pem' does not exist or is empty
Let's Encrypt uses a clever symlink structure to manage certificate renewals. The live directory contains symlinks pointing to actual certificate files in the archive directory. The common causes include:
- Permission issues preventing Apache from traversing the symlink chain
- SELinux/AppArmor blocking access (common on RedHat/Debian systems)
- Race conditions during certificate renewal
First, verify the actual file existence and permissions:
# Check symlink resolution
sudo ls -la /etc/letsencrypt/live/example.com/
# Verify file accessibility as Apache user
sudo -u www-data cat /etc/letsencrypt/live/example.com/fullchain.pem
# Check directory permissions (critical for symlink traversal)
namei -l /etc/letsencrypt/live/example.com/fullchain.pem
The most common root cause is the www-data user lacking execute permissions on parent directories. Let's fix this:
# Set correct permissions (adjust paths as needed)
sudo chmod 755 /etc/letsencrypt/{live,archive}
sudo chmod 755 /etc/letsencrypt/live/example.com
On security-enhanced systems, you might need additional steps:
# For SELinux systems:
sudo chcon -R -t httpd_sys_content_t /etc/letsencrypt/
# For AppArmor:
sudo aa-complain /etc/apparmor.d/usr.sbin.apache2
Ensure your SSL configuration points to the correct paths:
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
# Alternative modern syntax:
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>
To prevent future issues, add a pre-renewal hook in your certbot configuration:
# /etc/letsencrypt/renewal-hooks/pre/fix-perms.sh
#!/bin/bash
chmod 755 /etc/letsencrypt/{live,archive}
chmod 755 /etc/letsencrypt/live/*
The openssl error you're seeing suggests a trailing dot in the filename - this often indicates a shell expansion issue. Try:
# Use explicit quotes around path
sudo openssl x509 -text -noout -in "/etc/letsencrypt/live/example.com/fullchain.pem"
After implementing fixes, verify everything works:
# Test Apache configuration
sudo apachectl configtest
# Verify SSL handshake
openssl s_client -connect example.com:443 -servername example.com
# Check service status
systemctl status apache2
When running apachectl configtest
, you might encounter:
SSLCertificateFile: file '/etc/letsencrypt/live/www.example.com/fullchain.pem' does not exist or is empty
Yet your Apache server restarts fine and HTTPS works perfectly. This paradoxical situation typically occurs due to permission issues during certificate verification.
The key insight comes from the different user contexts:
# Works (root permissions)
sudo cat /etc/letsencrypt/live/example.com/fullchain.pem
# Fails (non-root user)
apachectl configtest
openssl x509 -text -noout -in /path/to/cert.pem
Check the actual permissions with:
ls -la /etc/letsencrypt/live/example.com/
You'll likely see symlinks with restrictive permissions. Let's Encrypt creates certificates in /etc/letsencrypt/archive/
and symlinks from live/
.
Execute these commands to resolve the issue:
sudo chmod 755 /etc/letsencrypt/{live,archive}
sudo chmod 644 /etc/letsencrypt/archive/example.com/privkey*.pem
sudo chmod 644 /etc/letsencrypt/archive/example.com/fullchain*.pem
Test with both privileged and non-privileged commands:
# Should now work without sudo
openssl x509 -text -noout -in /etc/letsencrypt/live/example.com/cert.pem
# Apache config test should pass
apachectl configtest
For cron jobs or automated renewals, ensure your script includes permission adjustments:
#!/bin/bash
certbot renew --quiet --post-hook "systemctl reload apache2 && \
chmod -R 755 /etc/letsencrypt/{live,archive} && \
chmod -R 644 /etc/letsencrypt/archive/*/privkey*.pem"
If using SELinux, you might need to adjust contexts instead:
sudo chcon -R -t cert_t /etc/letsencrypt/live/
sudo chcon -R -t cert_t /etc/letsencrypt/archive/