Manual SSL/TLS Configuration with Own Certificates
Introduction
While Let's Encrypt provides free automated SSL/TLS certificates, there are scenarios where you need to use your own certificates. Organizations may require Extended Validation (EV) or Organization Validation (OV) certificates for enhanced trust, internal Certificate Authorities (CA) for private networks, wildcard certificates from commercial providers, or certificates with longer validity periods for specific business requirements.
Manual SSL/TLS configuration gives you complete control over certificate generation, signing, and deployment. This approach is essential for enterprise environments, development/testing setups with self-signed certificates, multi-year commercial certificates, internal infrastructure not accessible from the internet, and compliance requirements mandating specific certificate authorities.
This comprehensive guide covers everything you need to know about manually configuring SSL/TLS certificates. You'll learn how to generate Certificate Signing Requests (CSR), create self-signed certificates for testing, install commercial SSL certificates, configure Apache and Nginx for HTTPS, implement certificate chains correctly, troubleshoot common certificate issues, and follow security best practices. Whether you're setting up a development environment or deploying enterprise-grade SSL infrastructure, this guide provides the foundation for successful manual certificate management.
Prerequisites
Before beginning manual SSL/TLS configuration, ensure you have:
- Apache or Nginx installed and running on your Linux server
- Root or sudo access to your server
- OpenSSL installed (pre-installed on most Linux distributions)
- Basic understanding of SSL/TLS concepts and Public Key Infrastructure (PKI)
- A registered domain name (for production certificates)
- Firewall configured to allow HTTPS traffic on port 443
- Text editor knowledge for configuration file editing
- Understanding of your web server's virtual host or server block configuration
Understanding SSL/TLS Certificates
Certificate Components
An SSL/TLS certificate contains:
Subject Information:
- Common Name (CN): Domain name (e.g., www.example.com)
- Organization (O): Company name
- Organizational Unit (OU): Department
- Locality (L): City
- State/Province (ST): State
- Country (C): Two-letter country code
Technical Elements:
- Public Key: Used for encryption
- Private Key: Must be kept secure, used for decryption
- Signature: CA's digital signature
- Validity Period: Start and expiration dates
- Serial Number: Unique identifier
- Extensions: Subject Alternative Names (SAN), Key Usage, etc.
Certificate Types
By Validation Level:
- Domain Validation (DV): Verifies domain ownership only
- Organization Validation (OV): Verifies domain and organization
- Extended Validation (EV): Highest validation, shows organization in browser
By Coverage:
- Single Domain: Covers one fully qualified domain name
- Wildcard: Covers domain and all subdomains (*.example.com)
- Multi-Domain (SAN): Covers multiple specific domains
By Purpose:
- Self-Signed: For testing/development
- Internal CA: For private networks
- Commercial CA: For public-facing websites
Installing OpenSSL
OpenSSL is typically pre-installed, but verify:
# Check OpenSSL version
openssl version
# Expected output:
# OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
# If not installed:
# Ubuntu/Debian
sudo apt update
sudo apt install openssl -y
# CentOS/Rocky/AlmaLinux
sudo yum install openssl -y # or dnf
Creating Self-Signed Certificates
Self-signed certificates are perfect for development and testing environments.
Generate Self-Signed Certificate (Single Command)
# Create directory for certificates
sudo mkdir -p /etc/ssl/private
sudo mkdir -p /etc/ssl/certs
# Generate self-signed certificate (valid for 365 days)
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/example.com.key \
-out /etc/ssl/certs/example.com.crt \
-subj "/C=US/ST=California/L=San Francisco/O=Example Inc/OU=IT/CN=example.com"
Parameters explained:
req: Certificate request command-x509: Create self-signed certificate-nodes: Don't encrypt private key-days 365: Valid for 365 days-newkey rsa:2048: Generate 2048-bit RSA key-keyout: Private key output file-out: Certificate output file-subj: Subject information (non-interactive)
Generate Self-Signed Certificate (Interactive)
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/example.com.key \
-out /etc/ssl/certs/example.com.crt
You'll be prompted for:
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]: California
Locality Name (eg, city) []: San Francisco
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Example Inc
Organizational Unit Name (eg, section) []: IT Department
Common Name (e.g. server FQDN or YOUR name) []: example.com
Email Address []: [email protected]
Wildcard Self-Signed Certificate
# Create wildcard certificate for *.example.com
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/wildcard.example.com.key \
-out /etc/ssl/certs/wildcard.example.com.crt \
-subj "/C=US/ST=California/L=San Francisco/O=Example Inc/CN=*.example.com"
Multi-Domain (SAN) Self-Signed Certificate
Create OpenSSL config file:
sudo nano /tmp/san.cnf
Add:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = US
ST = California
L = San Francisco
O = Example Inc
OU = IT Department
CN = example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = blog.example.com
DNS.4 = api.example.com
Generate certificate:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/example.com.key \
-out /etc/ssl/certs/example.com.crt \
-config /tmp/san.cnf \
-extensions v3_req
Secure Private Key
Set appropriate permissions:
# Private key should be readable only by root
sudo chmod 600 /etc/ssl/private/example.com.key
# Certificate can be world-readable
sudo chmod 644 /etc/ssl/certs/example.com.crt
# Verify permissions
ls -la /etc/ssl/private/example.com.key
ls -la /etc/ssl/certs/example.com.crt
Commercial Certificates: CSR Generation
For commercial SSL certificates, generate a Certificate Signing Request (CSR).
Generate Private Key and CSR
# Generate private key (2048-bit RSA)
sudo openssl genrsa -out /etc/ssl/private/example.com.key 2048
# Generate CSR
sudo openssl req -new \
-key /etc/ssl/private/example.com.key \
-out /tmp/example.com.csr \
-subj "/C=US/ST=California/L=San Francisco/O=Example Inc/OU=IT/CN=example.com/[email protected]"
Generate CSR Interactively
sudo openssl req -new \
-key /etc/ssl/private/example.com.key \
-out /tmp/example.com.csr
Answer prompts carefully (information will appear in certificate):
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]: California
Locality Name (eg, city) []: San Francisco
Organization Name (eg, company) []: Example Inc
Organizational Unit Name (eg, section) []: IT Department
Common Name (e.g. server FQDN or YOUR name) []: example.com
Email Address []: [email protected]
Please enter the following 'extra' attributes:
A challenge password []: [Leave blank]
An optional company name []: [Leave blank]
Generate CSR for Wildcard Certificate
sudo openssl req -new \
-key /etc/ssl/private/wildcard.example.com.key \
-out /tmp/wildcard.example.com.csr \
-subj "/C=US/ST=California/L=San Francisco/O=Example Inc/CN=*.example.com"
Generate CSR for Multi-Domain (SAN) Certificate
Create config file:
sudo nano /tmp/san_csr.cnf
Add:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = US
ST = California
L = San Francisco
O = Example Inc
OU = IT Department
CN = example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = blog.example.com
DNS.4 = api.example.com
DNS.5 = shop.example.com
Generate CSR:
# Generate private key first
sudo openssl genrsa -out /etc/ssl/private/example.com.key 2048
# Generate CSR with SAN
sudo openssl req -new \
-key /etc/ssl/private/example.com.key \
-out /tmp/example.com.csr \
-config /tmp/san_csr.cnf
View CSR Contents
# View CSR details
sudo openssl req -text -noout -in /tmp/example.com.csr
# View subject alternative names
sudo openssl req -text -noout -in /tmp/example.com.csr | grep -A 5 "Subject Alternative Name"
Submit CSR to Certificate Authority
- Copy CSR content:
cat /tmp/example.com.csr
- Submit to CA (Digicert, Sectigo, GlobalSign, etc.)
- Complete domain validation (email, DNS, or HTTP)
- Download signed certificate from CA
- Save certificate as
/etc/ssl/certs/example.com.crt
Installing Commercial SSL Certificates
Certificate Chain Structure
Commercial certificates come with:
- Primary Certificate: Your domain certificate (example.com.crt)
- Intermediate Certificate(s): CA intermediate certificates
- Root Certificate: CA root certificate (usually in browser)
Combine Certificate Chain
Most web servers need certificate + intermediate chain:
# Method 1: Concatenate files
cat /tmp/example.com.crt /tmp/intermediate.crt > /etc/ssl/certs/example.com-fullchain.crt
# Method 2: If CA provides bundle
cat /tmp/example.com.crt /tmp/ca-bundle.crt > /etc/ssl/certs/example.com-fullchain.crt
# Method 3: Manual creation
sudo nano /etc/ssl/certs/example.com-fullchain.crt
Add in this order:
-----BEGIN CERTIFICATE-----
[Your Domain Certificate]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[Intermediate Certificate 1]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[Intermediate Certificate 2 if exists]
-----END CERTIFICATE-----
Verify Certificate Chain
# Verify certificate
sudo openssl x509 -in /etc/ssl/certs/example.com.crt -text -noout
# Verify private key matches certificate
sudo openssl rsa -in /etc/ssl/private/example.com.key -check
sudo openssl x509 -noout -modulus -in /etc/ssl/certs/example.com.crt | openssl md5
sudo openssl rsa -noout -modulus -in /etc/ssl/private/example.com.key | openssl md5
# MD5 hashes should match
# Verify certificate chain
sudo openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt /etc/ssl/certs/example.com.crt
Apache SSL/TLS Configuration
Ubuntu/Debian Apache Configuration
Enable SSL module:
sudo a2enmod ssl
sudo systemctl restart apache2
Create SSL virtual host:
sudo nano /etc/apache2/sites-available/example.com-ssl.conf
Add configuration:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
ServerAdmin [email protected]
DocumentRoot /var/www/example.com/html
# SSL Engine
SSLEngine on
# Certificate files
SSLCertificateFile /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# If using separate intermediate certificate
SSLCertificateChainFile /etc/ssl/certs/intermediate.crt
# Or if using full chain
# SSLCertificateFile /etc/ssl/certs/example.com-fullchain.crt
# Modern SSL configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# OCSP Stapling
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# Security headers
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
</IfModule>
<Directory /var/www/example.com/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/example-ssl-access.log combined
</VirtualHost>
# HTTP to HTTPS redirect
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
Enable site and reload:
sudo a2ensite example.com-ssl.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
CentOS/Rocky/AlmaLinux Apache Configuration
Install mod_ssl:
sudo yum install mod_ssl -y # or dnf
Create SSL virtual host:
sudo nano /etc/httpd/conf.d/example.com-ssl.conf
Add same configuration as Ubuntu (adjust paths if needed).
Test and reload:
sudo httpd -t
sudo systemctl reload httpd
Nginx SSL/TLS Configuration
Ubuntu/Debian Nginx Configuration
Create SSL server block:
sudo nano /etc/nginx/sites-available/example.com-ssl
Add configuration:
# HTTPS server block
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html index.htm;
# SSL certificates
ssl_certificate /etc/ssl/certs/example.com-fullchain.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# SSL session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/ssl/certs/dhparam.pem;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example-ssl-access.log;
error_log /var/log/nginx/example-ssl-error.log;
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
Generate Diffie-Hellman parameters (one-time, takes several minutes):
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Enable and reload:
sudo ln -s /etc/nginx/sites-available/example.com-ssl /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
CentOS/Rocky/AlmaLinux Nginx Configuration
Create configuration:
sudo nano /etc/nginx/conf.d/example.com-ssl.conf
Add same configuration as Ubuntu, then:
sudo nginx -t
sudo systemctl reload nginx
Certificate Renewal
Manual Renewal Process
When certificate expires:
- Generate new CSR (can reuse private key):
sudo openssl req -new \
-key /etc/ssl/private/example.com.key \
-out /tmp/example.com-renewal.csr \
-subj "/C=US/ST=California/L=San Francisco/O=Example Inc/CN=example.com"
- Submit CSR to CA
- Download new certificate
- Install new certificate:
sudo cp /tmp/example.com-new.crt /etc/ssl/certs/example.com.crt
- Reload web server:
sudo systemctl reload apache2 # or nginx
Check Certificate Expiry
# Check certificate expiration date
sudo openssl x509 -in /etc/ssl/certs/example.com.crt -noout -enddate
# Check certificate details
sudo openssl x509 -in /etc/ssl/certs/example.com.crt -text -noout
# Check days until expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
Expiry Monitoring Script
sudo nano /usr/local/bin/check-ssl-expiry.sh
Add:
#!/bin/bash
CERT="/etc/ssl/certs/example.com.crt"
EXPIRY=$(openssl x509 -enddate -noout -in "$CERT" | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt 30 ]; then
echo "WARNING: SSL certificate expires in $DAYS_LEFT days!"
# Send email alert
echo "Certificate expires in $DAYS_LEFT days" | mail -s "SSL Certificate Warning" [email protected]
else
echo "SSL certificate is valid for $DAYS_LEFT more days"
fi
Make executable and add to cron:
sudo chmod +x /usr/local/bin/check-ssl-expiry.sh
sudo crontab -e
Add:
0 0 * * * /usr/local/bin/check-ssl-expiry.sh
Testing SSL/TLS Configuration
Command Line Testing
# Test SSL connection
openssl s_client -connect example.com:443 -servername example.com
# Check certificate chain
openssl s_client -connect example.com:443 -showcerts
# Test specific protocol
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Test cipher
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
# Full diagnostic
curl -I https://example.com
Online Testing Tools
- SSL Labs: https://www.ssllabs.com/ssltest/
- Mozilla Observatory: https://observatory.mozilla.org/
- Why No Padlock: https://www.whynopadlock.com/
Local Browser Testing
Access https://example.com and check:
- Valid certificate (green lock icon)
- Certificate details show correct information
- No mixed content warnings
- All resources loaded via HTTPS
Troubleshooting
Certificate Not Trusted
Symptoms: Browser shows "Not Secure" or certificate warning
Solutions:
- Verify certificate chain:
openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt /etc/ssl/certs/example.com.crt
- Ensure using fullchain:
# Check if certificate file includes chain
sudo openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/example.com.crt | openssl pkcs7 -print_certs -noout
- Check intermediate certificates installed
Private Key Mismatch
Symptoms: Web server won't start or SSL errors
Solution:
# Verify key matches certificate
sudo openssl x509 -noout -modulus -in /etc/ssl/certs/example.com.crt | openssl md5
sudo openssl rsa -noout -modulus -in /etc/ssl/private/example.com.key | openssl md5
# MD5 hashes must match
Permission Errors
Symptoms: Web server can't read certificate files
Solution:
# Fix permissions
sudo chmod 644 /etc/ssl/certs/example.com.crt
sudo chmod 600 /etc/ssl/private/example.com.key
sudo chown root:root /etc/ssl/private/example.com.key
# Verify
ls -la /etc/ssl/private/example.com.key
ls -la /etc/ssl/certs/example.com.crt
Mixed Content Warnings
Symptoms: HTTPS page loads HTTP resources
Solution:
- Update all resource URLs to HTTPS or relative URLs
- Check for hardcoded HTTP URLs in HTML, CSS, JavaScript
- Use Content-Security-Policy header to detect issues
Certificate Expired
Symptoms: Browser shows expired certificate error
Solution:
# Check expiry
sudo openssl x509 -in /etc/ssl/certs/example.com.crt -noout -enddate
# Renew certificate (see Certificate Renewal section)
Security Best Practices
Strong Private Key
# Generate strong 4096-bit RSA key
sudo openssl genrsa -out /etc/ssl/private/example.com.key 4096
# Or use ECDSA (smaller, faster, equally secure)
sudo openssl ecparam -genkey -name secp384r1 -out /etc/ssl/private/example.com-ecc.key
Secure Private Key Storage
# Restrictive permissions
sudo chmod 600 /etc/ssl/private/example.com.key
sudo chown root:root /etc/ssl/private/example.com.key
# Never commit to version control
# Add to .gitignore:
echo "*.key" >> .gitignore
# Backup encrypted
tar -czf - /etc/ssl/private/ | openssl enc -aes-256-cbc -out ssl-backup.tar.gz.enc
Regular Updates
- Monitor certificate expiry dates
- Renew certificates before expiration
- Update SSL/TLS configuration for latest security standards
- Review and update cipher suites regularly
Certificate Transparency
Check certificate in CT logs:
# Query CT logs
curl -s "https://crt.sh/?q=example.com&output=json" | jq
Best Practices Summary
Certificate Management
- Generate strong private keys (2048-bit minimum, 4096-bit recommended)
- Secure private keys with restrictive permissions (600)
- Keep private keys encrypted when backing up
- Never share private keys
- Use separate keys for different domains/services
Configuration
- Disable SSLv3, TLS 1.0, and TLS 1.1
- Use modern cipher suites
- Enable OCSP stapling
- Implement HSTS headers
- Enable HTTP/2
Monitoring
- Set up expiry alerts (30 days before)
- Regular SSL testing with online tools
- Monitor certificate transparency logs
- Keep documentation updated
- Test renewals before expiry
Conclusion
Manual SSL/TLS configuration provides complete control over your certificate infrastructure. This guide has covered self-signed certificates for development, commercial certificate installation, proper chain configuration, web server setup for Apache and Nginx, certificate renewal processes, and comprehensive troubleshooting.
Key takeaways:
- Self-signed certificates are perfect for development and testing
- Commercial certificates require proper CSR generation and chain installation
- Strong SSL/TLS configuration is essential for security
- Regular monitoring prevents certificate expiry issues
- Proper private key management is critical for security
Implement robust SSL/TLS configurations to protect user data, build trust, and meet compliance requirements. Continue exploring advanced topics like certificate pinning, CAA DNS records, multi-domain certificate management, and automated renewal systems for enterprise environments. Always follow security best practices and keep your SSL/TLS configurations updated for optimal protection.


