Set up production-grade SSL/TLS encryption for Node.js applications with certificate management, secure cipher suites, and comprehensive security headers including HSTS implementation.
Prerequisites
- Node.js 18+ installed
- Root or sudo access
- Domain name configured
- Basic Express.js knowledge
What this solves
This tutorial configures production-grade SSL/TLS security for Node.js applications running on Express.js. You'll implement secure certificate management, harden TLS protocols, configure strong cipher suites, and deploy essential security headers including HTTP Strict Transport Security (HSTS). This approach protects against man-in-the-middle attacks, ensures encrypted data transmission, and meets modern security compliance requirements.
Step-by-step configuration
Install required packages
Install Node.js security middleware and certificate management tools for production HTTPS implementation.
sudo apt update
sudo npm install -g helmet express-rate-limit express-slow-down
npm install helmet express-rate-limit express-slow-down morgan compression
Create SSL certificate directory structure
Establish secure directory structure for SSL certificates with appropriate permissions and ownership.
sudo mkdir -p /etc/ssl/nodejs/{certs,private}
sudo chown -R root:ssl-cert /etc/ssl/nodejs
sudo chmod 755 /etc/ssl/nodejs/certs
sudo chmod 750 /etc/ssl/nodejs/private
Generate self-signed certificate for development
Create self-signed certificates for development and testing environments. Replace with production certificates from a trusted CA.
sudo openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout /etc/ssl/nodejs/private/server.key \
-out /etc/ssl/nodejs/certs/server.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
Configure certificate permissions
Set secure permissions for SSL certificates ensuring only authorized users can access private keys.
sudo chmod 644 /etc/ssl/nodejs/certs/server.crt
sudo chmod 600 /etc/ssl/nodejs/private/server.key
sudo chown root:ssl-cert /etc/ssl/nodejs/private/server.key
Create hardened Express.js HTTPS server
Configure Express.js with production-grade SSL/TLS settings, security middleware, and comprehensive security headers.
const express = require('express');
const https = require('https');
const fs = require('fs');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const compression = require('compression');
const morgan = require('morgan');
const app = express();
// Security middleware configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
noSniff: true,
xssFilter: true,
referrerPolicy: { policy: 'same-origin' }
}));
// Rate limiting configuration
const limiter = rateLimit({
windowMs: 15 60 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests from this IP, please try again later.'
},
standardHeaders: true,
legacyHeaders: false
});
// Speed limiting configuration
const speedLimiter = slowDown({
windowMs: 15 60 1000, // 15 minutes
delayAfter: 50, // allow 50 requests per 15 minutes at full speed
delayMs: 500 // add 500ms delay after 50 requests
});
app.use(limiter);
app.use(speedLimiter);
app.use(compression());
app.use(morgan('combined'));
// SSL/TLS configuration with strong cipher suites
const sslOptions = {
key: fs.readFileSync('/etc/ssl/nodejs/private/server.key'),
cert: fs.readFileSync('/etc/ssl/nodejs/certs/server.crt'),
// TLS configuration
secureProtocol: 'TLSv1_2_method',
ciphers: [
'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'
].join(':'),
honorCipherOrder: true,
secureOptions: require('constants').SSL_OP_NO_SSLv2 |
require('constants').SSL_OP_NO_SSLv3 |
require('constants').SSL_OP_NO_TLSv1 |
require('constants').SSL_OP_NO_TLSv1_1
};
// Application routes
app.get('/', (req, res) => {
res.json({
message: 'Secure Node.js server running with HTTPS',
timestamp: new Date().toISOString(),
secure: req.secure,
protocol: req.protocol
});
});
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date().toISOString()
});
});
// HTTPS server startup
const server = https.createServer(sslOptions, app);
server.listen(443, () => {
console.log('Secure HTTPS server running on port 443');
console.log('TLS configuration: TLSv1.2+ with strong cipher suites');
console.log('Security headers: HSTS, CSP, X-Frame-Options enabled');
});
// HTTP to HTTPS redirect server
const httpApp = express();
httpApp.use((req, res) => {
res.redirect(301, https://${req.headers.host}${req.url});
});
httpApp.listen(80, () => {
console.log('HTTP redirect server running on port 80');
});
// Graceful shutdown handling
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
Create systemd service for Node.js HTTPS server
Configure systemd service to manage the Node.js HTTPS server with proper user permissions and automatic restart capabilities.
sudo useradd --system --shell /bin/false --home-dir /var/lib/nodejs --create-home nodejs
sudo usermod -a -G ssl-cert nodejs
[Unit]
Description=Node.js HTTPS Server
After=network.target
[Service]
Type=simple
User=nodejs
Group=nodejs
WorkingDirectory=/home/nodejs
ExecStart=/usr/bin/node secure-server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=443
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodejs-https
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/nodejs
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
Configure Node.js application ownership
Set proper ownership and permissions for the Node.js application files and directories.
sudo cp /home/nodejs/secure-server.js /home/nodejs/secure-server.js.bak
sudo chown -R nodejs:nodejs /home/nodejs
sudo chmod 755 /home/nodejs
sudo chmod 644 /home/nodejs/secure-server.js
Install and configure production certificates
Replace self-signed certificates with production certificates from Let's Encrypt or a trusted Certificate Authority.
sudo apt install -y certbot
sudo certbot certonly --standalone -d example.com
sudo ln -sf /etc/letsencrypt/live/example.com/fullchain.pem /etc/ssl/nodejs/certs/server.crt
sudo ln -sf /etc/letsencrypt/live/example.com/privkey.pem /etc/ssl/nodejs/private/server.key
Configure firewall rules for HTTPS
Open firewall ports for HTTP and HTTPS traffic while maintaining security.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status numbered
Enable and start the HTTPS service
Start the systemd service and enable automatic startup on boot.
sudo systemctl daemon-reload
sudo systemctl enable nodejs-https
sudo systemctl start nodejs-https
sudo systemctl status nodejs-https
Configure automatic certificate renewal
Set up automated certificate renewal with systemd timers to maintain continuous SSL coverage.
[Unit]
Description=Certbot Renewal Service
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
ExecStartPost=/bin/systemctl reload nodejs-https
[Unit]
Description=Run certbot renewal twice daily
[Timer]
OnCalendar=--* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl enable certbot-renewal.timer
sudo systemctl start certbot-renewal.timer
sudo systemctl list-timers certbot-renewal
Configure advanced TLS security settings
Implement OCSP stapling
Configure Online Certificate Status Protocol stapling for improved SSL performance and security validation.
const ocspCache = new Map();
const crypto = require('crypto');
// OCSP stapling configuration
const ocspOptions = {
cert: fs.readFileSync('/etc/ssl/nodejs/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/nodejs/private/server.key'),
ca: fs.readFileSync('/etc/ssl/certs/ca-certificates.crt'),
requestOCSP: true,
rejectUnauthorized: true
};
// Add to existing sslOptions in secure-server.js
Object.assign(sslOptions, ocspOptions);
Configure security monitoring
Set up logging and monitoring for SSL/TLS connections and security events. This integrates with existing Node.js monitoring solutions for comprehensive observability.
const winston = require('winston');
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: '/var/log/nodejs/security.log',
level: 'warn'
}),
new winston.transports.File({
filename: '/var/log/nodejs/ssl-access.log'
})
]
});
// Add security event logging middleware
app.use((req, res, next) => {
securityLogger.info({
type: 'ssl_connection',
ip: req.ip,
userAgent: req.get('User-Agent'),
protocol: req.protocol,
secure: req.secure,
timestamp: new Date().toISOString()
});
next();
});
Create log directories with proper permissions
Establish secure logging directories for SSL and security event monitoring.
sudo mkdir -p /var/log/nodejs
sudo chown nodejs:nodejs /var/log/nodejs
sudo chmod 755 /var/log/nodejs
Verify your setup
Test SSL certificate installation, TLS configuration, and security headers implementation.
# Check certificate validity
sudo openssl x509 -in /etc/ssl/nodejs/certs/server.crt -text -noout
Test HTTPS connection
curl -I https://example.com
Verify TLS configuration
echo | openssl s_client -connect example.com:443 -servername example.com
Check security headers
curl -I -H "Host: example.com" https://example.com
Verify service status
sudo systemctl status nodejs-https
Check SSL certificate expiration
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -dates -noout
Test security configuration
Validate SSL/TLS security implementation using online tools and command-line testing.
# Test SSL Labs rating (manual check at ssllabs.com/ssltest/)
Test HSTS preload eligibility
curl -H "Host: example.com" -I https://example.com | grep -i strict
Verify cipher suite strength
nmap --script ssl-enum-ciphers -p 443 example.com
Check certificate chain
echo | openssl s_client -connect example.com:443 -showcerts
Test HTTP to HTTPS redirect
curl -I http://example.com
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Permission denied accessing certificates | Incorrect file permissions or ownership | sudo chmod 600 /etc/ssl/nodejs/private/server.key && sudo chown root:ssl-cert /etc/ssl/nodejs/private/server.key |
| Node.js cannot bind to port 443 | Missing CAP_NET_BIND_SERVICE capability | Add AmbientCapabilities=CAP_NET_BIND_SERVICE to systemd service |
| SSL handshake failures | Weak cipher suites or outdated TLS version | Update cipher suite configuration and ensure TLSv1.2+ only |
| Certificate chain incomplete | Missing intermediate certificates | Use fullchain.pem from Let's Encrypt or concatenate intermediate certs |
| HSTS not working | Headers not properly configured | Verify helmet configuration includes HSTS settings |
| High memory usage | SSL session cache not optimized | Configure SSL session timeout and cache limits |
Next steps
- Configure NGINX reverse proxy with SSL termination for production load balancing
- Monitor Node.js applications with Prometheus for comprehensive performance tracking
- Configure advanced nftables logging for network security monitoring
- Implement advanced Node.js security middleware for additional protection layers
- Automate SSL certificate management with Let's Encrypt for production deployments
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Usage
usage() {
echo "Usage: $0 <domain_name> [port]"
echo "Example: $0 example.com 3443"
exit 1
}
# Check arguments
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
usage
fi
DOMAIN_NAME="$1"
PORT="${2:-3443}"
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Script failed. Cleaning up...${NC}"
rm -rf /tmp/nodejs-ssl-install
exit 1
}
trap cleanup ERR
# Check if running as root/sudo
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[ERROR] This script must be run as root or with sudo${NC}"
exit 1
fi
echo -e "${GREEN}Node.js SSL/HTTPS Security Hardening Installation${NC}"
echo "Domain: $DOMAIN_NAME"
echo "Port: $PORT"
echo ""
# Auto-detect distribution
echo -e "${YELLOW}[1/12] Detecting Linux distribution...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
SSL_GROUP="ssl-cert"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
SSL_GROUP="ssl-cert"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
SSL_GROUP="ssl-cert"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
echo "Detected: $PRETTY_NAME ($PKG_MGR)"
# Update system packages
echo -e "${YELLOW}[2/12] Updating system packages...${NC}"
$PKG_UPDATE
# Install Node.js and npm if not present
echo -e "${YELLOW}[3/12] Installing Node.js and npm...${NC}"
if ! command -v node &> /dev/null; then
case "$ID" in
ubuntu|debian)
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
$PKG_INSTALL nodejs
;;
almalinux|rocky|centos|rhel|ol|fedora|amzn)
curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
$PKG_INSTALL nodejs
;;
esac
else
echo "Node.js already installed"
fi
# Install OpenSSL
echo -e "${YELLOW}[4/12] Installing OpenSSL...${NC}"
$PKG_INSTALL openssl
# Create ssl-cert group if it doesn't exist
echo -e "${YELLOW}[5/12] Creating SSL certificate group...${NC}"
if ! getent group $SSL_GROUP > /dev/null 2>&1; then
groupadd $SSL_GROUP
fi
# Create SSL certificate directory structure
echo -e "${YELLOW}[6/12] Creating SSL certificate directories...${NC}"
mkdir -p /etc/ssl/nodejs/{certs,private}
chown -R root:$SSL_GROUP /etc/ssl/nodejs
chmod 755 /etc/ssl/nodejs
chmod 755 /etc/ssl/nodejs/certs
chmod 750 /etc/ssl/nodejs/private
# Generate self-signed certificate
echo -e "${YELLOW}[7/12] Generating SSL certificate...${NC}"
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout /etc/ssl/nodejs/private/server.key \
-out /etc/ssl/nodejs/certs/server.crt \
-subj "/C=US/ST=Production/L=Server/O=NodeJS-SSL/CN=$DOMAIN_NAME"
# Set certificate permissions
echo -e "${YELLOW}[8/12] Setting certificate permissions...${NC}"
chmod 644 /etc/ssl/nodejs/certs/server.crt
chmod 600 /etc/ssl/nodejs/private/server.key
chown root:$SSL_GROUP /etc/ssl/nodejs/private/server.key
# Create working directory
echo -e "${YELLOW}[9/12] Creating application directory...${NC}"
mkdir -p /opt/nodejs-ssl-app
cd /opt/nodejs-ssl-app
# Install Node.js packages
echo -e "${YELLOW}[10/12] Installing Node.js security packages...${NC}"
npm init -y
npm install helmet express-rate-limit express-slow-down morgan compression express
# Create hardened Express.js server
echo -e "${YELLOW}[11/12] Creating secure Express.js server...${NC}"
cat > /opt/nodejs-ssl-app/server.js << 'EOF'
const express = require('express');
const https = require('https');
const fs = require('fs');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const compression = require('compression');
const morgan = require('morgan');
const app = express();
// Security middleware configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
noSniff: true,
xssFilter: true,
referrerPolicy: { policy: 'same-origin' }
}));
// Rate limiting configuration
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: {
error: 'Too many requests from this IP, please try again later.'
},
standardHeaders: true,
legacyHeaders: false
});
// Speed limiting configuration
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50,
delayMs: 500
});
app.use(limiter);
app.use(speedLimiter);
app.use(compression());
app.use(morgan('combined'));
// SSL/TLS configuration
const sslOptions = {
key: fs.readFileSync('/etc/ssl/nodejs/private/server.key'),
cert: fs.readFileSync('/etc/ssl/nodejs/certs/server.crt'),
ciphers: [
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384'
].join(':'),
honorCipherOrder: true,
secureProtocol: 'TLSv1_2_method'
};
// Routes
app.get('/', (req, res) => {
res.json({
message: 'Secure Node.js server running!',
timestamp: new Date().toISOString(),
secure: req.secure
});
});
// Start HTTPS server
const PORT = process.env.PORT || 3443;
https.createServer(sslOptions, app).listen(PORT, () => {
console.log(`Secure server running on https://localhost:${PORT}`);
});
EOF
# Create systemd service
cat > /etc/systemd/system/nodejs-ssl-app.service << EOF
[Unit]
Description=Node.js SSL Application
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/nodejs-ssl-app
Environment=NODE_ENV=production
Environment=PORT=$PORT
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Set application permissions
chown -R root:root /opt/nodejs-ssl-app
chmod 755 /opt/nodejs-ssl-app
chmod 644 /opt/nodejs-ssl-app/server.js
# Configure firewall
echo -e "${YELLOW}[12/12] Configuring firewall...${NC}"
case "$ID" in
ubuntu|debian)
if command -v ufw &> /dev/null; then
ufw allow $PORT/tcp
fi
;;
almalinux|rocky|centos|rhel|ol|fedora)
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=$PORT/tcp
firewall-cmd --reload
fi
;;
esac
# Enable and start service
systemctl daemon-reload
systemctl enable nodejs-ssl-app
systemctl start nodejs-ssl-app
# Verification
echo -e "${GREEN}[SUCCESS] Installation completed!${NC}"
echo ""
echo "Verification:"
echo "- SSL certificate: $(ls -la /etc/ssl/nodejs/certs/server.crt)"
echo "- Private key: $(ls -la /etc/ssl/nodejs/private/server.key)"
echo "- Service status: $(systemctl is-active nodejs-ssl-app)"
echo "- Application directory: /opt/nodejs-ssl-app"
echo ""
echo "Your secure Node.js server is running at:"
echo "https://$DOMAIN_NAME:$PORT"
echo ""
echo "To test locally: curl -k https://localhost:$PORT"
Review the script before running. Execute with: bash install.sh