SSL certificates
SSL/TLS certificates provide an encrypted HTTPS connection between the client and server, protecting transmitted data from interception.
Configuration
To enable HTTPS, add a tls section to the server configuration:
{
"servers": {
"s1": {
"domains": ["example.com", "www.example.com"],
"ip": "0.0.0.0",
"port": 443,
"root": "/var/www/example.com/web",
"tls": {
"fullchain": "/etc/ssl/certs/fullchain.pem",
"private": "/etc/ssl/private/privkey.pem",
"ciphers": "TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 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"
},
"http": {
"routes": { ... }
}
}
}
}Parameters
All three fields are required — the server will not start if any of them is missing or empty.
| Parameter | Description |
|---|---|
fullchain | Path to the certificate file in PEM format (including intermediate certificates) |
private | Path to the private key file in PEM format |
ciphers | List of supported ciphers, passed to OpenSSL as is |
Protocol and ciphers
The server uses TLS_server_method(): SSLv2 and SSLv3 are disabled and renegotiation is forbidden. The ciphers string is a single string where names prefixed with TLS_ (TLS_AES_256_GCM_SHA384, …) are TLS 1.3 suites and the rest (ECDHE-RSA-AES256-GCM-SHA384, …) are TLS 1.2 suites. The separator may be a space or a colon.
Obtaining a certificate
Let's Encrypt (free)
Let's Encrypt provides free SSL certificates valid for 90 days with automatic renewal.
Installing Certbot
# Ubuntu/Debian
sudo apt update
sudo apt install certbot
# CentOS/RHEL
sudo yum install certbotObtaining a certificate
# Standalone mode (server must be stopped)
sudo certbot certonly --standalone -d example.com -d www.example.com
# Webroot mode (server is running)
sudo certbot certonly --webroot -w /var/www/example.com/web -d example.com -d www.example.comAfter successful issuance, certificates will be located at:
/etc/letsencrypt/live/example.com/fullchain.pem— certificate/etc/letsencrypt/live/example.com/privkey.pem— private key
Configuration with Let's Encrypt
{
"tls": {
"fullchain": "/etc/letsencrypt/live/example.com/fullchain.pem",
"private": "/etc/letsencrypt/live/example.com/privkey.pem",
"ciphers": "TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
}
}Automatic renewal
# Test renewal
sudo certbot renew --dry-run
# Add to cron (runs twice daily)
echo "0 0,12 * * * root certbot renew --quiet" | sudo tee /etc/cron.d/certbot-renewSelf-signed certificate
For development and testing, you can use a self-signed certificate:
# Create directory
sudo mkdir -p /etc/ssl/private
# Generate private key and certificate
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/selfsigned.key \
-out /etc/ssl/certs/selfsigned.crt \
-subj "/C=US/ST=California/L=San Francisco/O=Development/CN=localhost"
# Set permissions
sudo chmod 600 /etc/ssl/private/selfsigned.keyConfiguration
{
"tls": {
"fullchain": "/etc/ssl/certs/selfsigned.crt",
"private": "/etc/ssl/private/selfsigned.key",
"ciphers": "TLS_AES_256_GCM_SHA384 TLS_AES_128_GCM_SHA256"
}
}Warning
Self-signed certificates trigger browser warnings. Use them only for development.
Commercial certificate
When purchasing a certificate from a Certificate Authority (CA), you will receive:
- Domain certificate (
.crt) - Intermediate certificates (intermediate/chain)
- Private key (
.key)
Combining certificates
# Create fullchain from certificate and intermediates
cat domain.crt intermediate.crt > fullchain.pemRecommended ciphers
Modern configuration (TLS 1.3 + TLS 1.2)
TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 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-SHA256Maximum compatibility
TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 TLS_AES_128_CCM_8_SHA256 TLS_AES_128_CCM_SHA256 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:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256Multiple certificates on one IP (SNI)
The framework supports SNI (Server Name Indication). If several servers listen on the same ip and port, the certificate of the server whose domain pattern matches the host name sent by the client in SNI is selected during the TLS handshake. This lets you serve different certificates for different domains on the same address.
{
"servers": {
"site_a": {
"domains": ["a.example.com"],
"ip": "0.0.0.0",
"port": 443,
"tls": {
"fullchain": "/etc/ssl/a/fullchain.pem",
"private": "/etc/ssl/a/privkey.pem",
"ciphers": "..."
},
"http": { "routes": { ... } }
},
"site_b": {
"domains": ["b.example.com"],
"ip": "0.0.0.0",
"port": 443,
"tls": {
"fullchain": "/etc/ssl/b/fullchain.pem",
"private": "/etc/ssl/b/privkey.pem",
"ciphers": "..."
},
"http": { "routes": { ... } }
}
}
}Domain matching
SNI matching uses the same PCRE regular expressions as the server domains. IDN domains are automatically converted to Punycode, and IP addresses used as SNI are ignored per RFC 6066. If nothing matches, the default server's certificate (the one that accepted the connection) is used.
HTTP and HTTPS on the same server
To support both protocols, create two servers:
{
"servers": {
"http": {
"domains": ["example.com"],
"ip": "0.0.0.0",
"port": 80,
"http": {
"redirects": {
"/(.*)": "https://example.com/{1}"
}
}
},
"https": {
"domains": ["example.com"],
"ip": "0.0.0.0",
"port": 443,
"tls": {
"fullchain": "/etc/ssl/certs/fullchain.pem",
"private": "/etc/ssl/private/privkey.pem",
"ciphers": "..."
},
"http": {
"routes": { ... }
}
}
}
}Verifying the setup
Checking the certificate
# View certificate information
openssl x509 -in /etc/ssl/certs/fullchain.pem -text -noout
# Check expiration date
openssl x509 -in /etc/ssl/certs/fullchain.pem -enddate -noout
# Verify key and certificate match
openssl x509 -noout -modulus -in fullchain.pem | openssl md5
openssl rsa -noout -modulus -in privkey.pem | openssl md5
# Hashes should matchChecking the connection
# Check SSL connection
openssl s_client -connect example.com:443 -servername example.com
# Check supported protocols
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -tls1_2Online verification
- SSL Labs — detailed SSL configuration analysis
- SSL Checker — quick certificate check
Security
File permissions
# Certificate (can be public)
sudo chmod 644 /etc/ssl/certs/fullchain.pem
# Private key (root only)
sudo chmod 600 /etc/ssl/private/privkey.pem
sudo chown root:root /etc/ssl/private/privkey.pemSecurity headers
Add security headers via middleware:
int middleware_security_headers(httpctx_t* ctx) {
httpresponse_t* res = ctx->response;
// Force HTTPS usage
res->add_header(res, "Strict-Transport-Security", "max-age=31536000; includeSubDomains");
// Clickjacking protection
res->add_header(res, "X-Frame-Options", "SAMEORIGIN");
// XSS protection
res->add_header(res, "X-Content-Type-Options", "nosniff");
return 1;
}Troubleshooting
Error "certificate verify failed"
Make sure the fullchain.pem file contains the complete certificate chain (domain certificate + intermediates).
Error "key values mismatch"
The private key does not match the certificate. Verify the match using the command above.
Error "permission denied"
Check the permissions on the certificate and key files. The server process must have read access.
Certificate not updating
After replacing the certificate files, perform a hot reload — it re-reads config.json and builds new SSL contexts without restarting the process:
kill -SIGUSR1 $(pidof cpdy)The reload behavior is controlled by the main.reload setting in config.json:
"soft"— new connections use the updated configuration, existing connections keep running;"hard"— additionally forces the current sockets to close and clients to reconnect.