Skip to content

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.


Suppose you have 2 sites:

DomainRoot folder
site1.example.com/var/www/site1
site2.example.com/var/www/site2

Create the directories:

Terminal window
sudo mkdir -p /var/www/site1
sudo mkdir -p /var/www/site2

Add a test file in each:

Terminal window
echo "Hello from Site 1" | sudo tee /var/www/site1/index.html
echo "Hello from Site 2" | sudo tee /var/www/site2/index.html

Set ownership and permissions:

Terminal window
sudo chown -R www-data:www-data /var/www/site1 /var/www/site2
sudo chmod -R 755 /var/www/site1 /var/www/site2

2️⃣ Create server blocks for each site

Section titled “2️⃣ Create server blocks for each site”
sudo nano /etc/nginx/sites-available/site1.conf
server {
listen 80;
listen [::]:80;
server_name site1.example.com;
root /var/www/site1;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
sudo nano /etc/nginx/sites-available/site2.conf
server {
listen 80;
listen [::]:80;
server_name site2.example.com;
root /var/www/site2;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}

Terminal window
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:

Terminal window
sudo nginx -t
sudo systemctl reload nginx

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”
Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d site1.example.com -d site2.example.com

Certbot 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.


  • Each server_name gets 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 Host header.

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/site1
    • site2.evolo.fr → static files in /var/www/site2
    • site3.evolo.fr → backend app (Node.js/Python) on 127.0.0.1:3000
  • You want automatic HTTPS via Let’s Encrypt


Terminal window
sudo mkdir -p /var/www/site1 /var/www/site2
sudo chown -R www-data:www-data /var/www/site1 /var/www/site2
sudo chmod -R 755 /var/www/site1 /var/www/site2
# Add test pages
echo "Hello from Site 1" | sudo tee /var/www/site1/index.html
echo "Hello from Site 2" | sudo tee /var/www/site2/index.html

Create a single config file for all 3 sites:

Terminal window
sudo nano /etc/nginx/sites-available/multi-sites.conf

Paste:

# ------------------------
# 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;
}
}

Terminal window
sudo ln -s /etc/nginx/sites-available/multi-sites.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Terminal window
sudo apt install certbot python3-certbot-nginx
# For all 3 domains at once
sudo certbot --nginx -d site1.evolo.fr -d site2.evolo.fr -d site3.evolo.fr

Certbot will:

  • Create HTTPS server blocks
  • Redirect HTTP → HTTPS automatically
  • Configure SSL certificates

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;
}

  • 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?


Terminal window
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
}
#!/usr/bin/env bash
set -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.sh
Terminal window
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
}
#!/bin/bash
set -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
#!/bin/bash
set -e
BASE="/var/www/as1"
APP="$BASE/app"
PUBLIC="$BASE/public"
BACKUP="$BASE/backup"
echo "🚀 Deploy script started..."
# 1 — Backup current public folder
echo "📦 Backing up current public/ → backup/"
rm -rf "$BACKUP"/*
cp -r "$PUBLIC"/* "$BACKUP" 2>/dev/null || true
# 2 — No need for npm/pnpm, build happens locally
echo "📁 Source uploaded to app/, dist uploaded to public/"
# 3 — Nginx reload
echo "🔁 Reloading Nginx..."
sudo systemctl reload nginx
echo "🎉 Deployment complete!"
Terminal window
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;
}
#!/bin/bash
set -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 install
pnpm 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 << EOF
set -e
# Fix PATH for NVM & PNPM
export 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 login
export 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 1
fi
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