Set up NGINX with HTTP/3 and QUIC support for faster web performance. Configure SSL certificates, implement modern security headers like CSP and HSTS, and optimize for production workloads.
Prerequisites
- Root access to server
- Domain name with DNS configured
- Ports 80 and 443 accessible
What this solves
HTTP/3 and QUIC protocol provide faster web performance by reducing connection latency and improving reliability over unstable networks. This tutorial shows you how to install NGINX with HTTP/3 support, configure SSL certificates, and implement modern security headers for production-grade web hosting.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest NGINX version with HTTP/3 support.
sudo apt update && sudo apt upgrade -y
sudo dnf update -y
Install required dependencies
Install build tools and dependencies needed for NGINX with HTTP/3 support.
sudo apt install -y curl gnupg2 ca-certificates lsb-release software-properties-common
sudo dnf install -y curl gnupg2 ca-certificates epel-release
Add NGINX official repository
Add the official NGINX repository to get the latest version with HTTP/3 support compiled with the necessary modules.
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
sudo apt update
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo rpm --import -
sudo tee /etc/yum.repos.d/nginx.repo <
Install NGINX
Install NGINX from the official repository which includes HTTP/3 and QUIC support.
sudo apt install -y nginx
sudo dnf install -y nginx
Verify HTTP/3 support
Check that your NGINX installation includes HTTP/3 and QUIC support by examining the build configuration.
nginx -V 2>&1 | grep -o with-http_v3_module
nginx -V 2>&1 | grep -o with-http_quic_module
Install Certbot for SSL certificates
Install Certbot to automatically obtain and renew SSL certificates from Let's Encrypt.
sudo apt install -y certbot python3-certbot-nginx
sudo dnf install -y certbot python3-certbot-nginx
Configure firewall rules
Open the necessary ports for HTTP, HTTPS, and HTTP/3. HTTP/3 uses UDP port 443 instead of TCP.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload
Create initial server configuration
Create a basic server configuration file for your domain that will be enhanced with SSL and HTTP/3 support.
server {
listen 80;
server_name example.com www.example.com;
location / {
return 301 https://$server_name$request_uri;
}
location /.well-known/acme-challenge/ {
root /var/www/html;
}
}
Create web root directory
Create the web root directory and set proper ownership for the web server to serve files.
sudo mkdir -p /var/www/html
sudo chown -R nginx:nginx /var/www/html
sudo chmod -R 755 /var/www/html
echo "<h1>Welcome to NGINX with HTTP/3</h1>" | sudo tee /var/www/html/index.html
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 755 for directories and 644 for files.
Test and start NGINX
Test the configuration syntax and start NGINX service.
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl status nginx
Obtain SSL certificate
Use Certbot to obtain SSL certificates from Let's Encrypt for your domain.
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos -m admin@example.com
Configure NGINX with HTTP/3 and security headers
Replace the Certbot-generated configuration with a production-ready setup including HTTP/3, QUIC, and modern security headers.
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen 443 quic reuseport;
server_name example.com www.example.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HTTP/3 and QUIC Configuration
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# 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;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
# Performance Optimizations
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Document Root
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# Security: Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Configure main NGINX settings
Optimize the main NGINX configuration for better performance and HTTP/3 support.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# Performance Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;
client_max_body_size 20M;
# Hide NGINX version
server_tokens off;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=100r/m;
include /etc/nginx/conf.d/*.conf;
}
Test and reload configuration
Validate the new configuration and reload NGINX to apply the changes.
sudo nginx -t
sudo systemctl reload nginx
Set up automatic certificate renewal
Configure automatic SSL certificate renewal to ensure your certificates stay valid.
sudo systemctl enable --now certbot.timer
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
Verify your setup
Test that NGINX is working correctly with HTTP/3 support and security headers.
# Check NGINX status
sudo systemctl status nginx
# Test HTTP/3 support with curl (if available)
curl -I --http3 https://example.com
# Check security headers
curl -I https://example.com
# Verify SSL certificate
ssl-cert-check -c /etc/letsencrypt/live/example.com/fullchain.pem
# Test configuration syntax
sudo nginx -t
You can also use online tools like HTTP/3 Check (https://http3check.net) to verify HTTP/3 support and SSL Labs (https://www.ssllabs.com/ssltest/) to test your SSL configuration.
Performance optimization
Enable HTTP/3 prioritization
Configure HTTP/3 stream prioritization for better performance with multiple requests.
# HTTP/3 Optimization
http3_stream_buffer_size 64k;
http3_max_concurrent_pushes 20;
http3_push_preload on;
Configure caching headers
Add appropriate caching headers to improve performance for static assets.
# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
Common issues
Symptom Cause Fix
HTTP/3 not working Missing QUIC/HTTP3 modules Check nginx -V for http_v3_module and reinstall from official repo
SSL certificate errors Firewall blocking port 80 Ensure ports 80 and 443 (TCP/UDP) are open: sudo ufw status
Configuration test fails Syntax errors in config Run sudo nginx -t and fix reported errors
Permission denied errors Incorrect file ownership sudo chown -R nginx:nginx /var/www/html && sudo chmod -R 755 /var/www/html
QUIC connection fails UDP port 443 blocked Open UDP port 443: sudo ufw allow 443/udp
Security headers missing Headers not applied Check add_header directives are in correct server block
Next steps
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'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
DOMAIN=""
EMAIL=""
WEB_ROOT="/var/www/html"
NGINX_USER="nginx"
NGINX_CONFIG_DIR="/etc/nginx"
NGINX_SITE_CONFIG=""
PKG_MGR=""
PKG_INSTALL=""
PKG_UPDATE=""
FIREWALL_CMD=""
# Usage function
usage() {
echo "Usage: $0 -d <domain> -e <email> [-h]"
echo " -d <domain> Domain name (e.g., example.com)"
echo " -e <email> Email for Let's Encrypt registration"
echo " -h Show this help message"
exit 1
}
# Cleanup function for error handling
cleanup() {
echo -e "${RED}[ERROR]${NC} Script failed. Cleaning up..."
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl stop nginx
fi
exit 1
}
trap cleanup ERR
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}$1${NC}"
}
# Parse command line arguments
while getopts "d:e:h" opt; do
case $opt in
d) DOMAIN="$OPTARG" ;;
e) EMAIL="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required arguments
if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
log_error "Domain and email are required"
usage
fi
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Detect distribution and set package manager
log_step "[1/12] Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
FIREWALL_CMD="ufw"
NGINX_USER="www-data"
NGINX_SITE_CONFIG="$NGINX_CONFIG_DIR/sites-available/default"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
NGINX_SITE_CONFIG="$NGINX_CONFIG_DIR/conf.d/default.conf"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
NGINX_SITE_CONFIG="$NGINX_CONFIG_DIR/conf.d/default.conf"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_CMD="firewall-cmd"
NGINX_SITE_CONFIG="$NGINX_CONFIG_DIR/conf.d/default.conf"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected: $PRETTY_NAME"
else
log_error "Cannot detect distribution"
exit 1
fi
# Update system packages
log_step "[2/12] Updating system packages..."
$PKG_UPDATE
# Install dependencies
log_step "[3/12] Installing dependencies..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL curl gnupg2 ca-certificates lsb-release software-properties-common
else
$PKG_INSTALL curl gnupg2 ca-certificates epel-release
fi
# Add NGINX official repository
log_step "[4/12] Adding NGINX official repository..."
if [[ "$PKG_MGR" == "apt" ]]; then
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$ID $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx.list
apt update
else
curl -fsSL https://nginx.org/keys/nginx_signing.key | rpm --import -
cat > /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
fi
# Install NGINX
log_step "[5/12] Installing NGINX..."
$PKG_INSTALL nginx
# Verify HTTP/3 support
log_step "[6/12] Verifying NGINX HTTP/3 support..."
if nginx -V 2>&1 | grep -q "http_v3_module\|http_quic_module"; then
log_info "NGINX installed with HTTP/3 support"
else
log_warn "NGINX may not have HTTP/3 support compiled in"
fi
# Install Certbot
log_step "[7/12] Installing Certbot..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL certbot python3-certbot-nginx
else
$PKG_INSTALL certbot python3-certbot-nginx
fi
# Configure firewall
log_step "[8/12] Configuring firewall..."
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw --force enable
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 443/udp
ufw reload
else
systemctl enable --now firewalld
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=443/udp
firewall-cmd --reload
fi
# Create web root directory
log_step "[9/12] Creating web root directory..."
mkdir -p "$WEB_ROOT"
chown -R "$NGINX_USER":"$NGINX_USER" "$WEB_ROOT"
chmod -R 755 "$WEB_ROOT"
echo "Welcome to NGINX with HTTP/3" > "$WEB_ROOT/index.html"
chown "$NGINX_USER":"$NGINX_USER" "$WEB_ROOT/index.html"
chmod 644 "$WEB_ROOT/index.html"
# Create initial NGINX configuration
log_step "[10/12] Creating initial NGINX configuration..."
cat > "$NGINX_SITE_CONFIG" << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root $WEB_ROOT;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
location /.well-known/acme-challenge/ {
root $WEB_ROOT;
}
}
EOF
# Test and start NGINX
nginx -t
systemctl enable --now nginx
# Obtain SSL certificate
log_step "[11/12] Obtaining SSL certificate..."
certbot --nginx -d "$DOMAIN" -d "www.$DOMAIN" --non-interactive --agree-tos -m "$EMAIL" --redirect
# Configure NGINX with HTTP/3 and security headers
log_step "[12/12] Configuring NGINX with HTTP/3 and security headers..."
cat > "$NGINX_SITE_CONFIG" << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
listen 443 quic reuseport;
server_name $DOMAIN www.$DOMAIN;
root $WEB_ROOT;
index index.html;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HTTP/3 Advertisement
add_header Alt-Svc 'h3=":443"; ma=86400';
# 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 Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
# Test configuration and reload NGINX
nginx -t
systemctl reload nginx
# Verification
log_info "Installation completed successfully!"
log_info "Verifying installation..."
if systemctl is-active --quiet nginx; then
log_info "✓ NGINX is running"
else
log_error "✗ NGINX is not running"
fi
if curl -sSf "http://$DOMAIN" -o /dev/null 2>/dev/null; then
log_info "✓ HTTP redirect working"
else
log_warn "✗ HTTP not accessible"
fi
if curl -sSfk "https://$DOMAIN" -o /dev/null 2>/dev/null; then
log_info "✓ HTTPS working"
else
log_warn "✗ HTTPS not accessible"
fi
log_info "Setup complete! Your site should be available at: https://$DOMAIN"
log_info "SSL certificate will auto-renew via systemd timer"
Review the script before running. Execute with: bash install.sh