Skip to main content

VPS Server Setup: Nginx Deployment Configuration for Web Applications

A properly configured VPS (Virtual Private Server) forms the foundation of reliable web application hosting. This guide walks through setting up Ubuntu/Debian servers with Nginx, security hardening, and configurations that support both single and multiple applications.

Understanding VPS and Web Servers: Complete Beginner's Guide

What you'll learn:

  • What a VPS is and why you need one for web applications
  • What Nginx is and how it serves web applications
  • How to set up a secure, production-ready server from scratch
  • How to configure SSL certificates for HTTPS
  • How to host multiple applications on one server

Prerequisites:

  • Basic command line knowledge
  • A VPS from providers like DigitalOcean, Linode, or AWS
  • A domain name (optional but recommended)

What is a VPS? A VPS (Virtual Private Server) is like renting your own computer in the cloud. Unlike shared hosting where you share resources with others, a VPS gives you:

  • Your own operating system (usually Ubuntu Linux)
  • Dedicated resources (CPU, RAM, storage)
  • Root access to install and configure anything
  • Public IP address so people can visit your websites

Real-world analogy: Think of web hosting options like living arrangements:

  • Shared hosting = Living in a dorm room (cheap, limited control, shared resources)
  • VPS = Renting your own apartment (more control, dedicated space, reasonable cost)
  • Dedicated server = Owning a house (complete control, expensive, more maintenance)

What is Nginx? Nginx (pronounced "engine-x") is a web server—software that receives requests from web browsers and serves your website files. Think of it like a waiter in a restaurant:

  1. Takes orders (receives HTTP requests from browsers)
  2. Finds the right food (locates your website files)
  3. Serves the meal (sends HTML, CSS, JS files back to browsers)
  4. Handles special requests (routes API calls to your Node.js apps)

Why use Nginx?

  • Fast and efficient: Handles thousands of concurrent connections
  • Reverse proxy: Can forward requests to Node.js/Python applications
  • Static file serving: Serves images, CSS, JS files very efficiently
  • SSL termination: Handles HTTPS certificates and encryption
  • Load balancing: Can distribute traffic across multiple servers

Initial Server Setup

Connecting to Your VPS

Using SSH with password (initial connection):

ssh root@your-server-ip
# Or with custom user
ssh username@your-server-ip

First-time security steps:

# Update system packages
apt update && apt upgrade -y

# Create a non-root user (if not already created)
adduser deploy
usermod -aG sudo deploy

# Switch to new user
su - deploy

Essential System Packages

# Update package lists
sudo apt update

# Install essential packages
sudo apt install -y \
curl \
wget \
git \
unzip \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release

# Install build tools (for Node.js native modules)
sudo apt install -y build-essential

# Install fail2ban for security
sudo apt install -y fail2ban

Nginx Installation and Configuration

Installing Nginx

# Install Nginx
sudo apt install nginx -y

# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx

# Check status
sudo systemctl status nginx

Basic Nginx Configuration

Main configuration file: /etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
worker_connections 768;
use epoll;
multi_accept on;
}

http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;

# MIME
include /etc/nginx/mime.types;
default_type application/octet-stream;

# Logging Settings
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;

# Gzip Settings
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;

# Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

Directory Structure Setup

# Create web directories
sudo mkdir -p /var/www/html
sudo mkdir -p /var/log/nginx

# Set proper ownership
sudo chown -R www-data:www-data /var/www
sudo chown -R deploy:deploy /var/www

# Set permissions
sudo chmod -R 755 /var/www

Single Application Configuration

Basic Static Site Configuration

Create /etc/nginx/sites-available/default:

server {
listen 80 default_server;
listen [::]:80 default_server;

root /var/www/html;
index index.html index.htm index.nginx-debian.html;

server_name your-domain.com www.your-domain.com;

# Handle client-side routing (React Router, Vue Router)
location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# Security headers
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 "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}

React/Vue Application Configuration

Create /etc/nginx/sites-available/react-app:

server {
listen 80;
server_name your-app.com www.your-app.com;

root /var/www/react-app;
index index.html;

# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/x-javascript
application/xml+rss
application/javascript
application/json;

# Handle React Router
location / {
try_files $uri $uri/ /index.html;
}

# API proxy (if backend on same server)
location /api/ {
proxy_pass http://localhost: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;
}

# Static asset optimization
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# Disable access to source maps in production
location ~ \.map$ {
return 404;
}

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

Enable the site:

# Enable the configuration
sudo ln -s /etc/nginx/sites-available/react-app /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Multi-Application Configuration

Directory Structure for Multiple Apps

# Create directories for multiple applications
sudo mkdir -p /var/www/{main-site,admin-portal,api-dashboard,blog}

# Set ownership
sudo chown -R deploy:www-data /var/www/*

# Set permissions
sudo chmod -R 755 /var/www

Port-Based Multi-Application Setup

Main application (Port 80): Create /etc/nginx/sites-available/main-site:

server {
listen 80 default_server;
server_name your-domain.com www.your-domain.com;

root /var/www/main-site;
index index.html;

access_log /var/log/nginx/main-site-access.log;
error_log /var/log/nginx/main-site-error.log;

location / {
try_files $uri $uri/ /index.html;
}

# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

Admin portal (Port 8080): Create /etc/nginx/sites-available/admin-portal:

server {
listen 8080;
server_name your-domain.com www.your-domain.com _;

root /var/www/admin-portal;
index index.html;

access_log /var/log/nginx/admin-portal-access.log;
error_log /var/log/nginx/admin-portal-error.log;

# Basic authentication for admin access
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;

location / {
try_files $uri $uri/ /index.html;
}

# Static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

API Dashboard (Port 3000): Create /etc/nginx/sites-available/api-dashboard:

server {
listen 3000;
server_name your-domain.com _;

root /var/www/api-dashboard;
index index.html;

access_log /var/log/nginx/api-dashboard-access.log;
error_log /var/log/nginx/api-dashboard-error.log;

location / {
try_files $uri $uri/ /index.html;
}

# WebSocket support for real-time features
location /ws/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}

Subdomain-Based Multi-Application Setup

Main site configuration: Create /etc/nginx/sites-available/main-domain:

server {
listen 80;
server_name your-domain.com www.your-domain.com;

root /var/www/main-site;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}
}

Admin subdomain: Create /etc/nginx/sites-available/admin-subdomain:

server {
listen 80;
server_name admin.your-domain.com;

root /var/www/admin-portal;
index index.html;

# IP restriction for admin area
allow 192.168.1.0/24; # Office network
allow 10.0.0.0/8; # VPN network
deny all;

location / {
try_files $uri $uri/ /index.html;
}
}

API subdomain: Create /etc/nginx/sites-available/api-subdomain:

server {
listen 80;
server_name api.your-domain.com;

# Rate limiting for API
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location / {
limit_req zone=api burst=20 nodelay;

proxy_pass http://localhost:4000;
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;
}
}

Path-Based Multi-Application Setup

What is path-based configuration? Path-based configuration serves multiple applications from different URL paths on the same domain and port. Instead of using different ports or subdomains, each application is accessible via a different path like /admin/ or /dashboard/.

When to use path-based configuration:

  • You want all applications on the same domain
  • Limited to one SSL certificate
  • Prefer clean URLs without port numbers
  • Applications can handle base path configuration

Example path-based configuration: Create /etc/nginx/sites-available/multi-path-apps:

server {
listen 80;
server_name yourdomain.com www.yourdomain.com;

# Main application (root path)
location / {
root /var/www/main-site;
index index.html;
try_files $uri $uri/ /index.html;

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

# Admin portal at /admin/ path
location /admin/ {
alias /var/www/admin-portal/;
index index.html;
try_files $uri $uri/ /admin/index.html;

# Handle admin portal routing
location ~* ^/admin/(.*)$ {
try_files $uri $uri/ /admin/index.html;
}
}

# API dashboard at /dashboard/ path
location /dashboard/ {
alias /var/www/api-dashboard/;
index index.html;
try_files $uri $uri/ /dashboard/index.html;

# Handle dashboard routing
location ~* ^/dashboard/(.*)$ {
try_files $uri $uri/ /dashboard/index.html;
}
}

# boringdocs portal at /tools/ path
location /tools/ {
alias /var/www/boringdocs/;
index index.html;
try_files $uri $uri/ /tools/index.html;

# Handle tools routing
location ~* ^/tools/(.*)$ {
try_files $uri $uri/ /tools/index.html;
}
}

# Security headers for all paths
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

Access patterns for path-based setup:

  • Main site: http://yourdomain.com/
  • Admin portal: http://yourdomain.com/admin/
  • API dashboard: http://yourdomain.com/dashboard/
  • portal: http://yourdomain.com/tools/

Important considerations for React apps: Path-based routing requires special build configuration. Your React applications need to be built with the correct base path:

// For admin portal - vite.config.js
export default defineConfig({
base: '/admin/', // Set base path
// ... other config
});

// For dashboard - vite.config.js
export default defineConfig({
base: '/dashboard/', // Set base path
// ... other config
});

Enable All Configurations

# Enable all site configurations
sudo ln -s /etc/nginx/sites-available/main-site /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/admin-portal /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/api-dashboard /etc/nginx/sites-enabled/

# Test all configurations
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

SSL/HTTPS Configuration

Install Certbot for Let's Encrypt

# Install snapd (if not already installed)
sudo apt install snapd

# Install certbot
sudo snap install --classic certbot

# Create symlink
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Generate SSL Certificates

For single domain:

sudo certbot --nginx -d your-domain.com -d www.your-domain.com

For multiple domains:

sudo certbot --nginx -d your-domain.com -d www.your-domain.com -d admin.your-domain.com -d api.your-domain.com

For port-based applications:

# Generate certificate for main domain
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# Manually configure SSL for other ports

Manual SSL Configuration for Multiple Ports

Update main site with SSL:

server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;

ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

# SSL Configuration
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

root /var/www/main-site;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}
}

Admin portal with SSL on custom port:

server {
listen 8443 ssl http2;
server_name your-domain.com;

ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

root /var/www/admin-portal;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}
}

Auto-renewal Setup

# Test renewal
sudo certbot renew --dry-run

# Setup automatic renewal
sudo crontab -e

# Add this line to crontab
0 12 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx

Firewall Configuration

UFW (Uncomplicated Firewall) Setup

What is UFW? UFW (Uncomplicated Firewall) is like a security guard for your server. It blocks unwanted traffic and only allows connections you specifically permit. This protects your server from hackers and malicious traffic.

# Install UFW
sudo apt install ufw

# Default policies (block everything coming in, allow everything going out)
sudo ufw default deny incoming # Block all incoming connections by default
sudo ufw default allow outgoing # Allow all outgoing connections

# Allow SSH (do this before enabling UFW!)
sudo ufw allow ssh
sudo ufw allow 22

# Allow HTTP and HTTPS
sudo ufw allow 80
sudo ufw allow 443

# Allow custom application ports
sudo ufw allow 8080 # Admin portal
sudo ufw allow 3000 # API dashboard
sudo ufw allow 8443 # Admin portal SSL

# Or allow port ranges
sudo ufw allow 8000:9000/tcp

# Enable firewall
sudo ufw enable

# Check status
sudo ufw status verbose

Advanced Firewall Rules

# Allow from specific IP
sudo ufw allow from 192.168.1.100

# Allow specific port from specific IP
sudo ufw allow from 192.168.1.100 to any port 8080

# Allow subnet
sudo ufw allow from 192.168.1.0/24

# Rate limiting (basic DDoS protection)
sudo ufw limit ssh
sudo ufw limit 80
sudo ufw limit 443

Security Hardening

SSH Security

Edit SSH configuration (/etc/ssh/sshd_config):

# Disable root login
PermitRootLogin no

# Disable password authentication (use key-based only)
PasswordAuthentication no

# Change default port (optional)
Port 2222

# Allow only specific users
AllowUsers deploy

# Disable X11 forwarding
X11Forwarding no

# Set idle timeout
ClientAliveInterval 300
ClientAliveCountMax 2

Restart SSH service:

sudo systemctl restart ssh

Fail2Ban Configuration

Install and configure Fail2Ban:

sudo apt install fail2ban

# Create custom jail configuration
sudo nano /etc/fail2ban/jail.local

Basic Fail2Ban configuration (/etc/fail2ban/jail.local):

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd

[ssh]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/*error.log

[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/*access.log
maxretry = 6

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/*access.log
maxretry = 2

Start Fail2Ban:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Check status
sudo fail2ban-client status

System Updates and Monitoring

Automatic security updates:

sudo apt install unattended-upgrades

# Configure automatic updates
sudo dpkg-reconfigure -plow unattended-upgrades

Basic monitoring with htop:

sudo apt install htop

# Run htop to monitor system resources
htop

Log Management

Nginx Log Configuration

Custom log formats in /etc/nginx/nginx.conf:

http {
# Custom log format with more details
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';

# Access logs for different applications
access_log /var/log/nginx/access.log detailed;
}

Log Rotation

Configure logrotate for Nginx:

sudo nano /etc/logrotate.d/nginx

Logrotate configuration:

/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 644 www-data www-data
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}

Performance Optimization

Nginx Performance Tuning

Optimize /etc/nginx/nginx.conf:

# Adjust based on server resources
worker_processes auto;
worker_connections 1024;

# Enable file caching
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

# Buffer sizes
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;

# Timeouts
client_header_timeout 3m;
client_body_timeout 3m;
send_timeout 3m;

# Gzip compression
gzip_comp_level 6;
gzip_min_length 1000;

System Performance

Optimize system limits:

# Edit limits configuration
sudo nano /etc/security/limits.conf

# Add these lines
* soft nofile 65536
* hard nofile 65536
www-data soft nofile 65536
www-data hard nofile 65536

Optimize kernel parameters:

sudo nano /etc/sysctl.conf

# Add these optimizations
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 65536 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 5000

Backup and Recovery

Automated Backup Script

Create /home/deploy/backup.sh:

#!/bin/bash

BACKUP_DIR="/home/deploy/backups"
DATE=$(date +%Y%m%d_%H%M%S)
SITES_DIR="/var/www"
NGINX_DIR="/etc/nginx"

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup web files
tar -czf $BACKUP_DIR/websites_$DATE.tar.gz -C / var/www

# Backup Nginx configuration
tar -czf $BACKUP_DIR/nginx_config_$DATE.tar.gz -C / etc/nginx

# Backup SSL certificates
tar -czf $BACKUP_DIR/ssl_certs_$DATE.tar.gz -C / etc/letsencrypt

# Remove backups older than 30 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete

echo "Backup completed: $DATE"

Make script executable and schedule:

chmod +x /home/deploy/backup.sh

# Add to crontab
crontab -e

# Add daily backup at 2 AM
0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1

Troubleshooting Common Issues

Nginx Won't Start

Check configuration syntax:

sudo nginx -t

Check error logs:

sudo tail -f /var/log/nginx/error.log

Common issues:

  • Port conflicts (another service using port 80/443)
  • Configuration syntax errors
  • Missing SSL certificate files
  • Insufficient permissions on web directories

Port Conflicts

Check what's using a port:

sudo netstat -tulnp | grep :80
sudo ss -tulnp | grep :80

Kill process using port:

sudo kill -9 $(sudo lsof -t -i:80)

SSL Certificate Issues

Check certificate status:

sudo certbot certificates

Manual renewal:

sudo certbot renew --force-renewal

Check certificate expiration:

openssl x509 -in /etc/letsencrypt/live/your-domain.com/cert.pem -noout -dates

Performance Issues

Monitor server resources:

# CPU and memory usage
htop

# Disk usage
df -h

# Network connections
ss -tuln

# Check Nginx status
sudo systemctl status nginx

Nginx access logs analysis:

# Most requested pages
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# Top IP addresses
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# Response status codes
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

Advanced Troubleshooting: Multi-Application Configuration Issues

Understanding Configuration Conflicts

When hosting multiple applications on one server, conflicts can arise that prevent proper operation. Think of it like managing multiple restaurants in one building—each needs its own space, entrance, and utilities without interfering with others.

Diagnosing Port Conflicts

Problem Symptoms:

  • Nginx fails to start with "Address already in use" errors
  • Wrong application loads when visiting a URL
  • Some applications randomly become inaccessible

Quick Diagnosis Commands:

# Check what processes are using specific ports
sudo netstat -tlnp | grep :80 # Check port 80
sudo netstat -tlnp | grep :8080 # Check port 8080
sudo ss -tlnp | grep :3000 # Modern alternative to netstat

# Check all nginx configurations for port conflicts
sudo nginx -T | grep -n "listen.*80"

# Check which sites are enabled
ls -la /etc/nginx/sites-enabled/

Common Port Conflict Scenarios:

# ❌ WRONG: Multiple apps on same port
# /etc/nginx/sites-available/app1
server {
listen 80;
server_name yourdomain.com; # Both apps use same port + domain
root /var/www/app1;
}

# /etc/nginx/sites-available/app2
server {
listen 80;
server_name yourdomain.com; # CONFLICT! Same as app1
root /var/www/app2;
}

Solutions:

Option 1: Different Ports (Recommended)

# ✅ CORRECT: Each app on different port
# App 1 - Main website (port 80)
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/main-site;
}

# App 2 - Admin panel (port 8080)
server {
listen 8080;
server_name yourdomain.com; # Can use same domain
root /var/www/admin-panel;
}

# App 3 - Tools (port 8090)
server {
listen 8090;
server_name yourdomain.com;
root /var/www/tools;
}

Option 2: Different Subdomains

# ✅ CORRECT: Different subdomains on port 80
# Main site
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/main-site;
}

# Admin subdomain
server {
listen 80;
server_name admin.yourdomain.com;
root /var/www/admin-panel;
}

# Tools subdomain
server {
listen 80;
server_name tools.yourdomain.com;
root /var/www/tools;
}

Directory Permission Troubleshooting

Problem: Applications can't access their files or write logs

Diagnosis:

# Check directory ownership and permissions
ls -la /var/www/

# Check specific app directory
ls -la /var/www/your-app/

# Check log permissions
ls -la /var/log/nginx/

# Test write permissions
sudo -u www-data touch /var/www/your-app/test-write

Common Permission Issues:

# ❌ Wrong ownership - root owns everything
drwxr-xr-x 3 root root 4096 Dec 10 10:00 my-app/

# ❌ Wrong permissions - others can't read
drwx------ 3 deploy deploy 4096 Dec 10 10:00 my-app/

Fix Permission Issues:

# Set correct ownership (deploy user owns files, www-data group can read)
sudo chown -R deploy:www-data /var/www/my-app/

# Set correct permissions for directories (755 = rwxr-xr-x)
find /var/www/my-app -type d -exec chmod 755 {} \;

# Set correct permissions for files (644 = rw-r--r--)
find /var/www/my-app -type f -exec chmod 644 {} \;

# Create separate log directories per app
sudo mkdir -p /var/log/nginx/{main-site,admin-panel,tools}
sudo chown -R www-data:adm /var/log/nginx/

SSL Certificate Configuration Issues

Problem: SSL certificates not working correctly for multiple applications

Diagnosis:

# Check SSL certificate paths
sudo nginx -T | grep ssl_certificate

# Test SSL certificate validity
openssl x509 -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem -text -noout

# Check certificate expiration
sudo certbot certificates

# Test SSL configuration
curl -I https://yourdomain.com
curl -I https://admin.yourdomain.com

Common SSL Issues:

  1. Mixed HTTP/HTTPS access
  2. Certificate not covering subdomains
  3. Wrong certificate paths in Nginx config

Fix SSL Issues:

# Generate certificates for main domain and subdomains
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -d admin.yourdomain.com -d tools.yourdomain.com

# Or generate separate certificates per subdomain
sudo certbot --nginx -d admin.yourdomain.com
sudo certbot --nginx -d tools.yourdomain.com

# Force HTTPS redirect for all applications
# Add to each server block:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl;
server_name yourdomain.com;
# SSL configuration here
}

Configuration Testing Workflow

Before Making Changes:

# 1. Backup current working configuration
sudo cp -r /etc/nginx/sites-available /etc/nginx/sites-backup-$(date +%Y%m%d)

# 2. Test current configuration works
sudo nginx -t

# 3. Note currently working URLs
curl -I http://yourdomain.com
curl -I http://yourdomain.com:8080

After Making Changes:

# 1. Test new configuration syntax
sudo nginx -t

# 2. If test passes, reload gracefully (doesn't interrupt existing connections)
sudo systemctl reload nginx

# 3. If test fails, review errors and fix
sudo nginx -t # Shows specific error lines

# 4. Test all applications still work
curl -I http://yourdomain.com # Main site
curl -I http://yourdomain.com:8080 # Admin panel
curl -I http://yourdomain.com:8090 # Tools

If Something Goes Wrong:

# Quick restore from backup
sudo cp -r /etc/nginx/sites-backup-20231210/* /etc/nginx/sites-available/
sudo systemctl reload nginx

# Check nginx error log for details
sudo tail -f /var/log/nginx/error.log

# Check system log for nginx issues
sudo journalctl -u nginx.service --since "10 minutes ago"

Resource Monitoring and Optimization

Memory and CPU Usage

Monitor resource usage per application:

# Overall system resources
htop
free -h
df -h

# Nginx memory usage
ps aux | grep nginx

# Check for memory leaks (watch over time)
watch -n 5 free -h

When running multiple applications:

# Calculate total memory needs
# Example for 3 React apps + 2 Node.js apps:
# - React apps (static): ~10MB each = 30MB
# - Node.js apps: ~100MB each = 200MB
# - Nginx: ~20MB
# - System: ~200MB
# Total: ~450MB (recommend 1GB+ VPS)

# Monitor disk usage per application
du -sh /var/www/*

Log Management for Multiple Applications

Separate log files prevent conflicts:

# In each server block, use unique log files:
server {
listen 8080;
server_name yourdomain.com;

# Unique logs for this application
access_log /var/log/nginx/admin-panel-access.log;
error_log /var/log/nginx/admin-panel-error.log;

root /var/www/admin-panel;
}

Log rotation prevents disk space issues:

# Create log rotation config
sudo nano /etc/logrotate.d/nginx-multiapp

# Add this content:
/var/log/nginx/*-access.log /var/log/nginx/*-error.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 644 www-data www-data
postrotate
sudo systemctl reload nginx
endscript
}

# Test log rotation
sudo logrotate -d /etc/logrotate.d/nginx-multiapp

Health Monitoring Scripts

Create automated health checks:

#!/bin/bash
# File: /home/deploy/health-check.sh

# List of applications to check
declare -A apps=(
["Main Site"]="http://localhost:80"
["Admin Panel"]="http://localhost:8080"
["Tools"]="http://localhost:8090"
["API Server"]="http://localhost:3000"
)

echo "🔍 Health Check Report - $(date)"
echo "================================"

for name in "${!apps[@]}"; do
url="${apps[$name]}"

if curl -f -s --max-time 10 "$url" > /dev/null; then
echo "✅ $name - OK ($url)"
else
echo "❌ $name - DOWN ($url)"

# Optional: Send alert (example with Discord webhook)
# curl -X POST -H "Content-Type: application/json" \
# -d "{\"content\":\"🚨 $name is DOWN at $url\"}" \
# "$DISCORD_WEBHOOK_URL"
fi
done

echo ""
echo "📊 Server Resources:"
echo "Memory: $(free -h | awk 'NR==2{printf "%.1f%%", $3*100/$2 }')"
echo "Disk: $(df -h / | awk 'NR==2{print $5}')"
echo "Load: $(uptime | awk -F'load average:' '{print $2}')"

Schedule health checks:

# Make script executable
chmod +x /home/deploy/health-check.sh

# Add to crontab (runs every 5 minutes)
crontab -e

# Add this line:
*/5 * * * * /home/deploy/health-check.sh >> /var/log/health-check.log 2>&1

Emergency Recovery Procedures

When all applications are down:

# 1. Check if nginx is running
sudo systemctl status nginx

# 2. If stopped, check why it failed to start
sudo journalctl -u nginx.service --no-pager

# 3. Test configuration
sudo nginx -t

# 4. If configuration is broken, use emergency config
sudo cp /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf
sudo systemctl start nginx

# 5. If still failing, check disk space
df -h
# If disk is full, clean logs:
sudo find /var/log -name "*.log" -type f -size +100M -delete

When specific applications are down:

# 1. Check if specific site config is enabled
ls -la /etc/nginx/sites-enabled/your-app

# 2. Check application-specific logs
sudo tail -100 /var/log/nginx/your-app-error.log

# 3. Temporary disable problematic application
sudo rm /etc/nginx/sites-enabled/problematic-app
sudo systemctl reload nginx

# 4. Fix issues and re-enable
sudo ln -s /etc/nginx/sites-available/problematic-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

This comprehensive VPS configuration provides a solid foundation for hosting web applications with proper security, performance optimization, and multi-application support. With these troubleshooting tools and procedures, you can confidently diagnose and resolve issues that arise in production environments.