Troubleshooting Let’s Encrypt SSL Certificate File Not Found Errors Despite Working Configuration in Apache


3 views

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/