nginx
Ah, now you want Nginx to serve multiple websites (virtual hosts) from the same server — each with its own domain name and directory. This is called name-based virtual hosting. Here’s how to do it step by step.
1️⃣ Directory structure
Section titled “1️⃣ Directory structure”Suppose you have 2 sites:
| Domain | Root folder |
|---|---|
site1.example.com | /var/www/site1 |
site2.example.com | /var/www/site2 |
Create the directories:
sudo mkdir -p /var/www/site1sudo mkdir -p /var/www/site2Add a test file in each:
echo "Hello from Site 1" | sudo tee /var/www/site1/index.htmlecho "Hello from Site 2" | sudo tee /var/www/site2/index.htmlSet ownership and permissions:
sudo chown -R www-data:www-data /var/www/site1 /var/www/site2sudo chmod -R 755 /var/www/site1 /var/www/site22️⃣ Create server blocks for each site
Section titled “2️⃣ Create server blocks for each site”Site 1
Section titled “Site 1”sudo nano /etc/nginx/sites-available/site1.confserver { listen 80; listen [::]:80; server_name site1.example.com;
root /var/www/site1; index index.html index.htm;
location / { try_files $uri $uri/ =404; }}Site 2
Section titled “Site 2”sudo nano /etc/nginx/sites-available/site2.confserver { listen 80; listen [::]:80; server_name site2.example.com;
root /var/www/site2; index index.html index.htm;
location / { try_files $uri $uri/ =404; }}3️⃣ Enable the sites
Section titled “3️⃣ Enable the sites”sudo ln -s /etc/nginx/sites-available/site1.conf /etc/nginx/sites-enabled/sudo ln -s /etc/nginx/sites-available/site2.conf /etc/nginx/sites-enabled/Test Nginx config:
sudo nginx -tsudo systemctl reload nginx4️⃣ Update DNS
Section titled “4️⃣ Update DNS”Both domains must point to your server’s IP:
site1.example.com → <server-IP>site2.example.com → <server-IP>
5️⃣ Optional: HTTPS with Let’s Encrypt
Section titled “5️⃣ Optional: HTTPS with Let’s Encrypt”sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d site1.example.com -d site2.example.comCertbot can automatically configure HTTPS for multiple sites.
6️⃣ Optional: Serve multiple directories on different ports
Section titled “6️⃣ Optional: Serve multiple directories on different ports”You can also assign different ports:
server { listen 82; server_name site1.example.com; root /var/www/site1; index index.html;}
server { listen 83; server_name site2.example.com; root /var/www/site2; index index.html;}But in practice, name-based hosting on port 80/443 is preferred, because clients don’t need to type the port.
✅ Notes
Section titled “✅ Notes”- Each
server_namegets its own root folder. - You can combine with reverse proxy to point some domains to backend apps.
- Nginx automatically picks the server block based on the
Hostheader.
If you want, I can create a ready-to-copy Nginx configuration for 3+ domains with both static files and reverse proxy backends, all HTTPS-enabled, so you don’t have to modify anything manually.
Do you want me to do that?
Perfect! Let’s create a ready-to-use Nginx configuration that handles multiple domains, each with its own directory or backend, and HTTPS enabled.
I’ll assume:
-
3 domains for example:
site1.evolo.fr→ static files in/var/www/site1site2.evolo.fr→ static files in/var/www/site2site3.evolo.fr→ backend app (Node.js/Python) on127.0.0.1:3000
-
You want automatic HTTPS via Let’s Encrypt
1️⃣ Directory setup
Section titled “1️⃣ Directory setup”sudo mkdir -p /var/www/site1 /var/www/site2sudo chown -R www-data:www-data /var/www/site1 /var/www/site2sudo chmod -R 755 /var/www/site1 /var/www/site2
# Add test pagesecho "Hello from Site 1" | sudo tee /var/www/site1/index.htmlecho "Hello from Site 2" | sudo tee /var/www/site2/index.html2️⃣ Nginx config
Section titled “2️⃣ Nginx config”Create a single config file for all 3 sites:
sudo nano /etc/nginx/sites-available/multi-sites.confPaste:
# ------------------------# Site 1: Static# ------------------------server { listen 80; listen [::]:80; server_name site1.evolo.fr;
root /var/www/site1; index index.html index.htm;
location / { try_files $uri $uri/ =404; }}
# ------------------------# Site 2: Static# ------------------------server { listen 80; listen [::]:80; server_name site2.evolo.fr;
root /var/www/site2; index index.html index.htm;
location / { try_files $uri $uri/ =404; }}
# ------------------------# Site 3: Backend app# ------------------------server { listen 80; listen [::]:80; server_name site3.evolo.fr;
location / { proxy_pass http://127.0.0.1:3000; 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; }}3️⃣ Enable the config
Section titled “3️⃣ Enable the config”sudo ln -s /etc/nginx/sites-available/multi-sites.conf /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginx4️⃣ Obtain HTTPS via Let’s Encrypt
Section titled “4️⃣ Obtain HTTPS via Let’s Encrypt”sudo apt install certbot python3-certbot-nginx
# For all 3 domains at oncesudo certbot --nginx -d site1.evolo.fr -d site2.evolo.fr -d site3.evolo.frCertbot will:
- Create HTTPS server blocks
- Redirect HTTP → HTTPS automatically
- Configure SSL certificates
5️⃣ Optional: Force HTTPS manually
Section titled “5️⃣ Optional: Force HTTPS manually”If needed, add this server block before the others:
server { listen 80; listen [::]:80; server_name site1.evolo.fr site2.evolo.fr site3.evolo.fr; return 301 https://$host$request_uri;}✅ Advantages of this setup
Section titled “✅ Advantages of this setup”- Each domain has its own folder or backend
- HTTPS enabled automatically
- Works for static sites or reverse-proxied apps
- Easy to add more sites: just create a new
server {}block
I can also make a fully ready template for 5+ domains with both static and dynamic backends, including directory listing disabled, caching, and compression, so it’s production-ready.
Do you want me to do that?
✅ Example of setup
Section titled “✅ Example of setup”1-static
Section titled “1-static”/etc/nginx/sites-available/wik1
Section titled “/etc/nginx/sites-available/wik1”server { server_name wik1.evolo.fr; # Option A: serve site on HTTP (no redirect) root /var/www/wik1; index index.html;
location / { try_files $uri $uri/ /index.html; }
# Option B (recommended for production): # Redirect all HTTP → HTTPS # return 301 https://$host$request_uri;
listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/wik1.evolo.fr/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/wik1.evolo.fr/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}server { if ($host = wik1.evolo.fr) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name wik1.evolo.fr; return 404; # managed by Certbot}/wik1/src/deploy.sh (local)
Section titled “/wik1/src/deploy.sh (local)”#!/usr/bin/env bashset -euo pipefail
echo "Building Astro project…"npm run build
echo "Deploying to server…"rsync -avz \ -e "ssh -i ~/.ssh/web1.pem" \ ./dist/ \ ubuntu@op1.evolo.fr:/var/www/wik1/ \ --rsync-path="sudo rsync"
echo "Restarting service…"ssh -i ~/.ssh/web1.pem ubuntu@op1.evolo.fr "sudo service nginx restart"
echo "Done!"
#to run this script, use the command: bash deploy.sh - ./deploy.sh#make sure you have the right permissions to execute the script: chmod +x deploy.sh2-static
Section titled “2-static”/etc/nginx/sites-available/as1
Section titled “/etc/nginx/sites-available/as1”server { server_name as1.evolo.fr;
root /var/www/as1/public; index index.html;
location / { try_files $uri $uri/ /index.html; }
listen [::]:443 ssl; # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/as1.evolo.fr/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/as1.evolo.fr/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}server { if ($host = as1.evolo.fr) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; listen [::]:80; server_name as1.evolo.fr; return 404; # managed by Certbot}/as1/src/deploy.sh (local)
Section titled “/as1/src/deploy.sh (local)”#!/bin/bashset -e
SERVER="ubuntu@as1.evolo.fr"KEY="$HOME/.ssh/web1.pem"REMOTE_BASE="/var/www/as1"REMOTE_APP="$REMOTE_BASE/app"REMOTE_PUBLIC="$REMOTE_BASE/public"
echo "🚀 Building Astro..."pnpm build
echo "📤 Uploading application source (excluding heavy directories)..."rsync -avz \ --exclude node_modules \ --exclude dist \ --exclude .git \ -e "ssh -i $KEY" \ ./ "$SERVER:$REMOTE_APP"
echo "📤 Uploading built static site to public/"rsync -avz \ -e "ssh -i $KEY" \ dist/ "$SERVER:$REMOTE_PUBLIC"
echo "▶️ Running remote deployment script..."ssh -i $KEY $SERVER "cd $REMOTE_BASE && ./deploy.sh"
echo "✅ Deploy finished successfully!"
#to run this script, use the command: bash deploy.sh - ./deploy.sh#make sure you have the right permissions to execute the script: chmod +x deploy.sh/var/www/as1/deploy.sh (server)
Section titled “/var/www/as1/deploy.sh (server)”#!/bin/bashset -e
BASE="/var/www/as1"APP="$BASE/app"PUBLIC="$BASE/public"BACKUP="$BASE/backup"
echo "🚀 Deploy script started..."
# 1 — Backup current public folderecho "📦 Backing up current public/ → backup/"rm -rf "$BACKUP"/*cp -r "$PUBLIC"/* "$BACKUP" 2>/dev/null || true
# 2 — No need for npm/pnpm, build happens locallyecho "📁 Source uploaded to app/, dist uploaded to public/"
# 3 — Nginx reloadecho "🔁 Reloading Nginx..."sudo systemctl reload nginx
echo "🎉 Deployment complete!"3-dynamic
Section titled “3-dynamic”/etc/nginx/sites-available/test1
Section titled “/etc/nginx/sites-available/test1”server { listen 443 ssl http2; server_name test1.evolo.fr;
ssl_certificate /etc/letsencrypt/live/test1.evolo.fr/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/test1.evolo.fr/privkey.pem;
# ===== STATIC FILES FROM ASTRO BUILD ===== location /_astro/ { alias /var/www/test1/public/client/_astro/; try_files $uri =404; }
location /assets/ { alias /var/www/test1/public/client/assets/; try_files $uri =404; }
# ===== NODE SSR APPLICATION ===== location / { proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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;
# Important for Clerk WebSocket support proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }}
server { listen 80; server_name test1.evolo.fr;
location /.well-known/acme-challenge/ { root /var/www/html; }
return 301 https://$host$request_uri;}/test1/src/deploy.sh (local)
Section titled “/test1/src/deploy.sh (local)”#!/bin/bashset -e
# -----------------------------# CONFIGURATION# -----------------------------SERVER="ubuntu@test1.evolo.fr"KEY="$HOME/.ssh/web1.pem"REMOTE_BASE="/var/www/test1"REMOTE_APP="$REMOTE_BASE/app"REMOTE_PUBLIC="$REMOTE_BASE/public"APP_NAME="test1"APP_PORT=3001
# -----------------------------# LOCAL BUILD# -----------------------------echo "🚀 Building Astro locally..."pnpm installpnpm build
# -----------------------------# UPLOAD APP SOURCE# -----------------------------echo "📤 Uploading application source (excluding node_modules and .git)..."rsync -avz \ --exclude node_modules \ --exclude .git \ -e "ssh -i $KEY" \ ./ "$SERVER:$REMOTE_APP"
# -----------------------------# UPLOAD BUILT DIST (client assets)# -----------------------------echo "📤 Uploading built client files to public/client..."rsync -avz \ -e "ssh -i $KEY" \ dist/client/ "$SERVER:$REMOTE_PUBLIC/client"
# -----------------------------# REMOTE DEPLOYMENT# -----------------------------echo "▶️ Running remote deployment on server..."ssh -i $KEY $SERVER bash << EOFset -e
# Fix PATH for NVM & PNPMexport PATH="\$HOME/.nvm/versions/node/v24.11.1/bin:\$HOME/.local/share/pnpm:\$PATH"
cd "$REMOTE_APP"
# -----------------------------# Ensure pnpm is installed# -----------------------------if ! command -v pnpm >/dev/null 2>&1; then echo "📦 Installing pnpm..." curl -fsSL https://get.pnpm.io/install.sh | sh export PATH="\$HOME/.local/share/pnpm/global/5/bin:\$PATH"fi
# Make sure PATH contains pnpm after SSH loginexport PATH="\$HOME/.local/share/pnpm/global/5/bin:\$PATH"
# -----------------------------# Install dependencies# -----------------------------echo "📦 Installing dependencies..."npx pnpm install --no-frozen-lockfile
# -----------------------------# Start / Restart app with pm2# -----------------------------echo "🚀 Starting Astro SSR server with pm2 on port $APP_PORT..."
SSR_ENTRY="./dist/server/entry.mjs"
if [ ! -f "\$SSR_ENTRY" ]; then echo "❌ ERROR: SSR entry not found at \$SSR_ENTRY" exit 1fi
if command -v pm2 >/dev/null 2>&1; then pm2 stop $APP_NAME || true pm2 delete $APP_NAME || true
PORT=$APP_PORT \ HOST=0.0.0.0 \ PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_Y2FsbS1waWthLTcwLmNsZXJrLmFjY291bnRzLmRldiQ \ CLERK_SECRET_KEY=sk_test_7xD5iajRJt9DrghyAdNfCTDeiEUhUnWfZLC2eGqjNb \ pm2 start "\$SSR_ENTRY" \ --name "$APP_NAME" \ --node-args="--experimental-vm-modules"
else echo "⚠️ pm2 not installed. Running in background using nohup..."
nohup PORT=$APP_PORT HOST=0.0.0.0 \ PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_Y2FsbS1waWthLTcwLmNsZXJrLmFjY291bnRzLmRldiQ \ CLERK_SECRET_KEY=sk_test_7xD5iajRJt9DrghyAdNfCTDeiEUhUnWfZLC2eGqjNb \ node "\$SSR_ENTRY" > app.log 2>&1 &fi
EOF
echo "✅ Deploy finished successfully!"# -----------------------------to run: ./deploy.sh