Created
November 20, 2025 05:02
-
-
Save PrashantBhatasana/d16cd49b73100d9fde929bc267bc1b3c to your computer and use it in GitHub Desktop.
Complete Nginx + Certbot SSL Setup Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ================================================ | |
| # Multi-Domain Nginx SSL Management | |
| # ================================================ | |
| # Manage multiple domains with different configurations | |
| # ================================================ | |
| set -e | |
| # Colors | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' | |
| # Configuration file | |
| CONFIG_FILE="/etc/nginx/domains.conf" | |
| NGINX_DIR="/etc/nginx" | |
| # Function to add a new domain | |
| add_domain() { | |
| local DOMAIN=$1 | |
| local EMAIL=$2 | |
| local PROXY_PORT=${3:-3000} | |
| local DOMAIN_TYPE=${4:-"proxy"} # proxy, static, redirect | |
| echo -e "${BLUE}Adding domain: ${DOMAIN}${NC}" | |
| # Create configuration based on type | |
| case $DOMAIN_TYPE in | |
| proxy) | |
| create_proxy_config $DOMAIN $EMAIL $PROXY_PORT | |
| ;; | |
| static) | |
| create_static_config $DOMAIN $EMAIL | |
| ;; | |
| redirect) | |
| local REDIRECT_TO=$5 | |
| create_redirect_config $DOMAIN $EMAIL $REDIRECT_TO | |
| ;; | |
| *) | |
| echo -e "${RED}Unknown domain type: ${DOMAIN_TYPE}${NC}" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # Function to create proxy configuration | |
| create_proxy_config() { | |
| local DOMAIN=$1 | |
| local EMAIL=$2 | |
| local PORT=$3 | |
| cat > ${NGINX_DIR}/sites-available/${DOMAIN} << EOF | |
| # Auto-generated Nginx configuration for ${DOMAIN} | |
| # Type: Reverse Proxy | |
| # Backend Port: ${PORT} | |
| # Generated: $(date) | |
| server { | |
| listen 80; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| return 301 https://\$server_name\$request_uri; | |
| } | |
| server { | |
| listen 443 ssl http2; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| # SSL Configuration (managed by Certbot) | |
| ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; | |
| # SSL Security Settings | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_ciphers HIGH:!aNULL:!MD5; | |
| ssl_prefer_server_ciphers off; | |
| ssl_session_cache shared:SSL:10m; | |
| ssl_session_timeout 10m; | |
| ssl_session_tickets off; | |
| ssl_stapling on; | |
| ssl_stapling_verify on; | |
| # Security Headers | |
| add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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; | |
| # Logging | |
| access_log /var/log/nginx/${DOMAIN}_access.log; | |
| error_log /var/log/nginx/${DOMAIN}_error.log; | |
| # Proxy Configuration | |
| location / { | |
| proxy_pass http://localhost:${PORT}; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade \$http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| proxy_set_header Host \$host; | |
| proxy_set_header X-Real-IP \$remote_addr; | |
| proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | |
| proxy_set_header X-Forwarded-Proto \$scheme; | |
| proxy_cache_bypass \$http_upgrade; | |
| # Timeouts | |
| proxy_connect_timeout 60s; | |
| proxy_send_timeout 60s; | |
| proxy_read_timeout 60s; | |
| # Buffering | |
| proxy_buffering off; | |
| proxy_request_buffering off; | |
| } | |
| # Health check endpoint | |
| location /health { | |
| access_log off; | |
| return 200 "healthy\\n"; | |
| add_header Content-Type text/plain; | |
| } | |
| } | |
| EOF | |
| # Enable site | |
| ln -sf ${NGINX_DIR}/sites-available/${DOMAIN} ${NGINX_DIR}/sites-enabled/ | |
| # Obtain SSL certificate | |
| certbot certonly --nginx --non-interactive --agree-tos --email ${EMAIL} -d ${DOMAIN} -d www.${DOMAIN} | |
| # Reload Nginx | |
| nginx -t && systemctl reload nginx | |
| echo -e "${GREEN}✅ Proxy configuration created for ${DOMAIN} -> localhost:${PORT}${NC}" | |
| } | |
| # Function to create static site configuration | |
| create_static_config() { | |
| local DOMAIN=$1 | |
| local EMAIL=$2 | |
| local WEB_ROOT="/var/www/${DOMAIN}" | |
| # Create web root | |
| mkdir -p ${WEB_ROOT} | |
| cat > ${NGINX_DIR}/sites-available/${DOMAIN} << EOF | |
| # Auto-generated Nginx configuration for ${DOMAIN} | |
| # Type: Static Website | |
| # Web Root: ${WEB_ROOT} | |
| # Generated: $(date) | |
| server { | |
| listen 80; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| return 301 https://\$server_name\$request_uri; | |
| } | |
| server { | |
| listen 443 ssl http2; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| # SSL Configuration (managed by Certbot) | |
| ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; | |
| # SSL Security Settings | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_ciphers HIGH:!aNULL:!MD5; | |
| # Security Headers | |
| add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; | |
| add_header X-Frame-Options "SAMEORIGIN" always; | |
| add_header X-Content-Type-Options "nosniff" always; | |
| # Web Root | |
| root ${WEB_ROOT}; | |
| index index.html index.htm; | |
| # Logging | |
| access_log /var/log/nginx/${DOMAIN}_access.log; | |
| error_log /var/log/nginx/${DOMAIN}_error.log; | |
| location / { | |
| try_files \$uri \$uri/ =404; | |
| } | |
| # Cache static assets | |
| location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { | |
| expires 30d; | |
| add_header Cache-Control "public, immutable"; | |
| } | |
| # Gzip compression | |
| 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; | |
| } | |
| EOF | |
| # Create default index.html | |
| cat > ${WEB_ROOT}/index.html << EOF | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>${DOMAIN}</title> | |
| </head> | |
| <body> | |
| <h1>Welcome to ${DOMAIN}</h1> | |
| <p>Your static website is ready!</p> | |
| </body> | |
| </html> | |
| EOF | |
| # Set permissions | |
| chown -R www-data:www-data ${WEB_ROOT} | |
| # Enable site | |
| ln -sf ${NGINX_DIR}/sites-available/${DOMAIN} ${NGINX_DIR}/sites-enabled/ | |
| # Obtain SSL certificate | |
| certbot certonly --nginx --non-interactive --agree-tos --email ${EMAIL} -d ${DOMAIN} -d www.${DOMAIN} | |
| # Reload Nginx | |
| nginx -t && systemctl reload nginx | |
| echo -e "${GREEN}✅ Static site configuration created for ${DOMAIN}${NC}" | |
| } | |
| # Function to create redirect configuration | |
| create_redirect_config() { | |
| local DOMAIN=$1 | |
| local EMAIL=$2 | |
| local REDIRECT_TO=$3 | |
| cat > ${NGINX_DIR}/sites-available/${DOMAIN} << EOF | |
| # Auto-generated Nginx configuration for ${DOMAIN} | |
| # Type: Redirect | |
| # Redirect To: ${REDIRECT_TO} | |
| # Generated: $(date) | |
| server { | |
| listen 80; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| return 301 https://${REDIRECT_TO}\$request_uri; | |
| } | |
| server { | |
| listen 443 ssl http2; | |
| server_name ${DOMAIN} www.${DOMAIN}; | |
| # SSL Configuration (managed by Certbot) | |
| ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; | |
| # Permanent redirect | |
| return 301 https://${REDIRECT_TO}\$request_uri; | |
| } | |
| EOF | |
| # Enable site | |
| ln -sf ${NGINX_DIR}/sites-available/${DOMAIN} ${NGINX_DIR}/sites-enabled/ | |
| # Obtain SSL certificate | |
| certbot certonly --nginx --non-interactive --agree-tos --email ${EMAIL} -d ${DOMAIN} -d www.${DOMAIN} | |
| # Reload Nginx | |
| nginx -t && systemctl reload nginx | |
| echo -e "${GREEN}✅ Redirect configuration created: ${DOMAIN} -> ${REDIRECT_TO}${NC}" | |
| } | |
| # Function to remove a domain | |
| remove_domain() { | |
| local DOMAIN=$1 | |
| echo -e "${YELLOW}Removing domain: ${DOMAIN}${NC}" | |
| # Remove Nginx configuration | |
| rm -f ${NGINX_DIR}/sites-enabled/${DOMAIN} | |
| rm -f ${NGINX_DIR}/sites-available/${DOMAIN} | |
| # Remove web root if exists | |
| rm -rf /var/www/${DOMAIN} | |
| # Revoke certificate (optional) | |
| read -p "Revoke SSL certificate? (y/n): " REVOKE | |
| if [ "$REVOKE" == "y" ]; then | |
| certbot revoke --cert-path /etc/letsencrypt/live/${DOMAIN}/cert.pem | |
| certbot delete --cert-name ${DOMAIN} | |
| fi | |
| # Reload Nginx | |
| nginx -t && systemctl reload nginx | |
| echo -e "${GREEN}✅ Domain ${DOMAIN} removed${NC}" | |
| } | |
| # Function to list all domains | |
| list_domains() { | |
| echo -e "${BLUE}Configured Domains:${NC}" | |
| echo "" | |
| for config in ${NGINX_DIR}/sites-enabled/*; do | |
| if [ -f "$config" ]; then | |
| DOMAIN=$(basename $config) | |
| echo -e "${GREEN}• ${DOMAIN}${NC}" | |
| # Check SSL certificate | |
| if [ -d "/etc/letsencrypt/live/${DOMAIN}" ]; then | |
| EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${DOMAIN}/cert.pem" | cut -d= -f2) | |
| echo " SSL Expiry: ${EXPIRY}" | |
| fi | |
| # Check if site is responding | |
| if curl -sSf -o /dev/null "https://${DOMAIN}" 2>/dev/null; then | |
| echo " Status: ✅ Online" | |
| else | |
| echo " Status: ❌ Offline" | |
| fi | |
| echo "" | |
| fi | |
| done | |
| } | |
| # Function to renew all SSL certificates | |
| renew_certificates() { | |
| echo -e "${BLUE}Renewing SSL certificates...${NC}" | |
| certbot renew | |
| systemctl reload nginx | |
| echo -e "${GREEN}✅ Certificate renewal complete${NC}" | |
| } | |
| # Function to backup configurations | |
| backup_nginx() { | |
| local BACKUP_DIR="/backup/nginx/$(date +%Y%m%d_%H%M%S)" | |
| echo -e "${BLUE}Backing up Nginx configurations...${NC}" | |
| mkdir -p ${BACKUP_DIR} | |
| # Backup Nginx configs | |
| cp -r ${NGINX_DIR}/sites-available ${BACKUP_DIR}/ | |
| cp -r ${NGINX_DIR}/sites-enabled ${BACKUP_DIR}/ | |
| # Backup SSL certificates | |
| cp -r /etc/letsencrypt ${BACKUP_DIR}/ | |
| # Create tar archive | |
| tar -czf ${BACKUP_DIR}.tar.gz -C ${BACKUP_DIR} . | |
| rm -rf ${BACKUP_DIR} | |
| echo -e "${GREEN}✅ Backup saved to ${BACKUP_DIR}.tar.gz${NC}" | |
| } | |
| # Main menu | |
| show_menu() { | |
| echo -e "${BLUE}================================================${NC}" | |
| echo -e "${BLUE}Multi-Domain Nginx Management${NC}" | |
| echo -e "${BLUE}================================================${NC}" | |
| echo "" | |
| echo "1. Add proxy domain (app backend)" | |
| echo "2. Add static website" | |
| echo "3. Add redirect domain" | |
| echo "4. Remove domain" | |
| echo "5. List all domains" | |
| echo "6. Renew SSL certificates" | |
| echo "7. Backup configurations" | |
| echo "8. Exit" | |
| echo "" | |
| read -p "Select option: " OPTION | |
| case $OPTION in | |
| 1) | |
| read -p "Enter domain: " DOMAIN | |
| read -p "Enter email: " EMAIL | |
| read -p "Enter backend port (default 3000): " PORT | |
| PORT=${PORT:-3000} | |
| add_domain $DOMAIN $EMAIL $PORT "proxy" | |
| ;; | |
| 2) | |
| read -p "Enter domain: " DOMAIN | |
| read -p "Enter email: " EMAIL | |
| add_domain $DOMAIN $EMAIL 0 "static" | |
| ;; | |
| 3) | |
| read -p "Enter domain: " DOMAIN | |
| read -p "Enter email: " EMAIL | |
| read -p "Redirect to domain: " REDIRECT_TO | |
| add_domain $DOMAIN $EMAIL 0 "redirect" $REDIRECT_TO | |
| ;; | |
| 4) | |
| read -p "Enter domain to remove: " DOMAIN | |
| remove_domain $DOMAIN | |
| ;; | |
| 5) | |
| list_domains | |
| ;; | |
| 6) | |
| renew_certificates | |
| ;; | |
| 7) | |
| backup_nginx | |
| ;; | |
| 8) | |
| exit 0 | |
| ;; | |
| *) | |
| echo -e "${RED}Invalid option${NC}" | |
| ;; | |
| esac | |
| } | |
| # Command line interface | |
| if [ "$1" == "add-proxy" ]; then | |
| add_domain $2 $3 ${4:-3000} "proxy" | |
| elif [ "$1" == "add-static" ]; then | |
| add_domain $2 $3 0 "static" | |
| elif [ "$1" == "add-redirect" ]; then | |
| add_domain $2 $3 0 "redirect" $4 | |
| elif [ "$1" == "remove" ]; then | |
| remove_domain $2 | |
| elif [ "$1" == "list" ]; then | |
| list_domains | |
| elif [ "$1" == "renew" ]; then | |
| renew_certificates | |
| elif [ "$1" == "backup" ]; then | |
| backup_nginx | |
| else | |
| show_menu | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment