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:
- Takes orders (receives HTTP requests from browsers)
- Finds the right food (locates your website files)
- Serves the meal (sends HTML, CSS, JS files back to browsers)
- 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:
- Mixed HTTP/HTTPS access
- Certificate not covering subdomains
- 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.