Domain Configuration: Single and Multiple Apps
What You'll Learn
- How to configure a custom domain for a single application
- How to configure DNS when running multiple apps on one server
- Understanding how Nginx routes traffic to different applications
- Setting up domain names and subdomains for your apps
- Troubleshooting common domain configuration issues
Understanding Domain Configuration
Whether you're hosting a single application or multiple applications on your server, you need to connect your domain name to your server's IP address. This guide covers both scenarios, starting with the simpler single-app setup and progressing to multiple apps.
Single app scenario:
You have one application (like a portfolio website) running on port 3000, and you want it accessible at myportfolio.com
.
Multiple apps scenario: You have several applications on one server:
- A portfolio website on port 3000
- An API service on port 4000
- A blog on port 5000
Each app needs its own domain name (like portfolio.com
, api.myservice.com
, blog.com
), but they all live on the same physical server with one IP address.
Think of it like an apartment building: The street address is your server's IP address. With a single app, you have one tenant. With multiple apps, you have multiple units, and the building manager (Nginx) directs visitors to the right apartment.
Why This Approach Works
The simple answer: All your applications share the same server, so they share the same IP address.
Why this works:
- Your server has one public IP address (like
123.45.67.89
) - Multiple domain names can point to the same IP address
- A reverse proxy (like Nginx) reads the domain name in each request and routes it to the correct application
Benefits of this approach:
- Cost-effective: One server instead of multiple servers
- Easier management: All apps in one place
- Resource sharing: Apps can share server resources efficiently
- Simplified SSL: One server to manage certificates for
How Domain Routing Works
Let's break down what happens when someone visits your website:
Step 1: DNS Resolution
When a user types boringdocs.dev
in their browser:
- Browser asks DNS: "What IP address is boringdocs.dev?"
- DNS responds: "It's at 123.45.67.89"
- Browser connects: Opens connection to 123.45.67.89
Step 2: HTTP Request
The browser sends a request that looks like this:
GET / HTTP/1.1
Host: boringdocs.dev
User-Agent: Mozilla/5.0...
Notice the Host: boringdocs.dev
line. This tells the server which domain the user is trying to reach.
Step 3: Nginx Routing
Nginx receives the request and:
- Reads the Host header: "This request is for boringdocs.dev"
- Checks server blocks: Looks for a configuration matching
boringdocs.dev
- Routes to correct app: Forwards the request to the right port (e.g., port 3000)
- Returns response: Sends the app's response back to the user
Visual representation:
User Browser → DNS Lookup → Server IP (123.45.67.89)
↓
Nginx (Port 80/443)
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
App 1 (Port 3000) App 2 (Port 4000) App 3 (Port 5000)
domain1.com api.domain.com domain2.com
Configuring DNS for Your Domain
Finding Your Server's IP Address
First, you need to know your server's public IP address. Run this command on your server:
curl ifconfig.me
Example output:
123.45.67.89
This is the IP address you'll use for all your domains.
Adding DNS Records
Go to your domain registrar or DNS provider (like Cloudflare, Namecheap, GoDaddy) and add A records:
For a main domain (boringdocs.dev):
Type | Name | Value | TTL |
---|---|---|---|
A | @ | 123.45.67.89 | 3600 |
A | www | 123.45.67.89 | 3600 |
What this means:
@
represents the root domain (boringdocs.dev)www
creates the www subdomain (www.boringdocs.dev)- Both point to the same IP address
- TTL (Time To Live) is how long DNS servers cache this information (in seconds)
For subdomains:
Type | Name | Value | TTL |
---|---|---|---|
A | api | 123.45.67.89 | 3600 |
A | blog | 123.45.67.89 | 3600 |
Important: DNS changes take time to spread across the internet. In practice, you'll usually see changes within 5-30 minutes, though technically it can take up to 48 hours in rare cases.
Verifying DNS Configuration
After adding DNS records, verify they're working:
# Check if domain resolves to your IP
nslookup boringdocs.dev
# Or use dig for more details
dig boringdocs.dev +short
Expected output:
123.45.67.89
If you see your server's IP address, DNS is configured correctly.
Configuring Nginx: Single App Setup
Let's start with the simpler scenario: one application, one domain.
Single App Configuration
If you have just one application running on your server, the configuration is straightforward. Nginx will serve this app for any request that comes to your server.
Example: Portfolio website on port 3000
Create your Nginx configuration file:
sudo nano /etc/nginx/sites-available/myportfolio
Add this configuration:
server {
# Listen on standard HTTP port
listen 80;
listen [::]:80; # IPv6 support
# Your domain name
server_name myportfolio.com www.myportfolio.com;
# Forward all requests to your app
location / {
proxy_pass http://localhost:3000;
# Preserve original request information
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;
}
}
Enable the configuration:
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/myportfolio /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
That's it! Your app is now accessible at http://myportfolio.com
.
When You Only Have One App
With a single app, you can also make it the default_server
, which means it will respond to:
- Your domain name (
myportfolio.com
) - The server's IP address (
123.45.67.89
) - Any other domain pointing to your server
server {
listen 80 default_server; # Add default_server
listen [::]:80 default_server;
server_name myportfolio.com www.myportfolio.com;
# ... rest of config
}
This is useful for testing before your DNS fully propagates.
Configuring Nginx: Multiple Apps Setup
Now let's look at the more complex scenario: multiple applications on one server.
Understanding Nginx Server Blocks
Nginx uses "server blocks" to handle multiple domains on the same IP. Each server block defines how to handle requests for specific domain names.
Key concept: Multiple server blocks can listen on the same port (80 or 443), and Nginx routes based on the server_name
directive.
Basic Server Block Structure
Here's what a server block looks like:
server {
# What port to listen on
listen 80;
listen [::]:80; # IPv6 support
# Which domain names this block handles
server_name boringdocs.dev www.boringdocs.dev;
# Where to send the traffic
location / {
proxy_pass http://localhost:3000;
# Additional proxy settings...
}
}
Breaking it down:
listen 80
: Accept HTTP connections on port 80listen [::]:80
: Also accept IPv6 connectionsserver_name
: The domain(s) this configuration handleslocation /
: Rules for all URLs (the/
means "everything")proxy_pass
: Forward requests to your app running on port 3000
Example: Multiple Apps Configuration
Let's say you have three apps:
App 1: Main website (boringdocs.dev) on port 3000
# File: /etc/nginx/sites-available/boringdocs
server {
listen 80;
listen [::]:80;
server_name boringdocs.dev www.boringdocs.dev;
location / {
# Forward all requests to the Next.js app on port 3000
proxy_pass http://localhost:3000;
# Preserve the original request information
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;
}
}
App 2: API service (api.boringdocs.dev) on port 4000
# File: /etc/nginx/sites-available/boringdocs-api
server {
listen 80;
listen [::]:80;
server_name api.boringdocs.dev;
location / {
proxy_pass http://localhost:4000;
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;
}
}
App 3: Another domain (example.com) on port 5000
# File: /etc/nginx/sites-available/example
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:5000;
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;
}
}
Understanding Proxy Headers
The proxy_set_header
directives preserve important information about the original request:
Header | Purpose | Why It Matters |
---|---|---|
Host | Original domain name | Your app knows which domain was requested |
X-Real-IP | User's actual IP address | For logging and security |
X-Forwarded-For | Chain of proxy IPs | Tracks the request path |
X-Forwarded-Proto | HTTP or HTTPS | App knows if connection was secure |
Upgrade | WebSocket upgrade requests | Enables real-time features |
Connection | Connection type | Required for WebSockets |
Why these matter:
Without these headers, your application would think all requests come from localhost
(127.0.0.1) instead of the actual user's IP address.
Enabling Your Configuration
After creating configuration files, you need to enable them:
# Create symbolic link to enable the site
sudo ln -s /etc/nginx/sites-available/boringdocs /etc/nginx/sites-enabled/
# Test configuration for syntax errors
sudo nginx -t
# If test passes, reload Nginx
sudo systemctl reload nginx
What each command does:
ln -s
: Creates a symbolic link (like a shortcut) fromsites-available
tosites-enabled
nginx -t
: Tests configuration without applying it (safe to run)systemctl reload
: Applies new configuration without dropping connections
Common Configuration Patterns
Pattern 1: Main Domain + Subdomains
All apps under one domain:
# Main site
server_name boringdocs.dev www.boringdocs.dev;
proxy_pass http://localhost:3000;
# API subdomain
server_name api.boringdocs.dev;
proxy_pass http://localhost:4000;
# Admin subdomain
server_name admin.boringdocs.dev;
proxy_pass http://localhost:5000;
DNS setup:
A @ → 123.45.67.89
A www → 123.45.67.89
A api → 123.45.67.89
A admin → 123.45.67.89
Pattern 2: Multiple Independent Domains
Each app has its own domain:
# Domain 1
server_name boringdocs.dev www.boringdocs.dev;
proxy_pass http://localhost:3000;
# Domain 2
server_name myshop.com www.myshop.com;
proxy_pass http://localhost:4000;
# Domain 3
server_name myblog.net www.myblog.net;
proxy_pass http://localhost:5000;
DNS setup: Each domain's DNS points to the same server IP.
Pattern 3: Path-Based Routing
Different apps on different URL paths (less common):
server {
server_name boringdocs.dev;
# Main app
location / {
proxy_pass http://localhost:3000;
}
# API on /api path
location /api {
proxy_pass http://localhost:4000;
}
# Admin on /admin path
location /admin {
proxy_pass http://localhost:5000;
}
}
Note: Path-based routing can be tricky with modern frameworks that have their own routing. I recommend using separate domains or subdomains instead.
Troubleshooting Common Issues
Issue 1: 404 Not Found (Nginx Error)
Symptoms:
- Browser shows "404 Not Found" with Nginx branding
- Your app is running but not accessible
Diagnosis:
Let's figure out what's causing this. The 404 means Nginx received the request but couldn't find what to do with it.
Common causes and solutions:
Cause 1: Wrong port in Nginx configuration
# Check what ports your apps are using
sudo netstat -tlnp | grep LISTEN
Look for your app in the output:
tcp6 0 0 :::3000 :::* LISTEN 12345/next-server
Make sure your Nginx proxy_pass
uses the correct port (http://localhost:3000
).
Cause 2: Nginx listening on wrong port
Your configuration might have:
listen 8080; # Wrong - should be 80
It should be:
listen 80; # Correct
Cause 3: App not running
Check if your app is actually running:
# Check running processes
ps aux | grep node # For Node.js apps
ps aux | grep python # For Python apps
If your app isn't running, start it:
# Example for Node.js app
cd /path/to/your/app
npm start
# or
node server.js
Cause 4: Configuration not enabled
# Check if config is in sites-enabled
ls -la /etc/nginx/sites-enabled/
# If missing, create the symbolic link
sudo ln -s /etc/nginx/sites-available/your-config /etc/nginx/sites-enabled/
# Reload Nginx
sudo systemctl reload nginx
Issue 2: Domain Not Resolving
Symptoms:
- Browser shows "This site can't be reached"
- DNS lookup fails
Diagnosis:
This usually means DNS isn't pointing to your server yet, or the changes haven't propagated.
Solution:
# Check DNS propagation
nslookup boringdocs.dev
# Or use online tools
# Visit: https://dnschecker.org
If DNS isn't resolving:
- Double-check that you added the A records in your DNS provider's control panel
- Wait for DNS propagation (usually 5-30 minutes, sometimes longer)
- Clear your local DNS cache so your computer checks for the new records:
# Windows
ipconfig /flushdns
# macOS
sudo dscacheutil -flushcache
# Linux
sudo systemd-resolve --flush-caches
Issue 3: Wrong App Responding
Symptoms:
- Visiting
domain1.com
shows content fromdomain2.com
- Multiple domains show the same app
Diagnosis:
This happens when Nginx can't tell which server block to use, so it picks the first one it finds (usually the default).
Root Cause: Nginx is matching the wrong server block.
Solution:
Check your server_name
directives:
# View all server names in enabled configs
grep -r "server_name" /etc/nginx/sites-enabled/
Make sure:
- Each domain appears in only one server block
- No typos in domain names
- No conflicting wildcard patterns
Example of conflict:
# Config 1
server_name *.boringdocs.dev; # Catches all subdomains
# Config 2
server_name api.boringdocs.dev; # Will never match because Config 1 catches it first
Fix: Be more specific or reorder configurations.
Issue 4: SSL/HTTPS Not Working
Symptoms:
- HTTP works but HTTPS doesn't
- Browser shows security warnings
Diagnosis:
HTTPS requires SSL certificates, and you probably haven't set them up yet. This is normal for a new server.
Solution:
Let's set up free SSL certificates using Let's Encrypt:
# Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx
# Get certificate for your domain
sudo certbot --nginx -d boringdocs.dev -d www.boringdocs.dev
# Certbot will automatically:
# 1. Obtain SSL certificate
# 2. Update your Nginx config
# 3. Set up auto-renewal
After running Certbot, your Nginx config will have:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name boringdocs.dev www.boringdocs.dev;
ssl_certificate /etc/letsencrypt/live/boringdocs.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/boringdocs.dev/privkey.pem;
location / {
proxy_pass http://localhost:3000;
# ... proxy headers
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name boringdocs.dev www.boringdocs.dev;
return 301 https://$server_name$request_uri;
}
Issue 5: Changes Not Taking Effect
Symptoms:
- You edited Nginx config but nothing changed
- Old configuration still active
Diagnosis:
Nginx doesn't automatically reload when you edit files. You need to tell it to reload the configuration.
Solution:
# Always test configuration first
sudo nginx -t
# If test passes, reload
sudo systemctl reload nginx
# If reload doesn't work, restart
sudo systemctl restart nginx
# Check for errors
sudo systemctl status nginx
sudo tail -f /var/log/nginx/error.log
Checking Your Configuration
Verify Apps Are Running
# See all listening ports
sudo netstat -tlnp | grep LISTEN
# Expected output shows your apps:
# tcp6 0 0 :::3000 :::* LISTEN 12345/next-server
# tcp6 0 0 :::4000 :::* LISTEN 12346/node
# tcp6 0 0 :::5000 :::* LISTEN 12347/python
Verify Nginx Configuration
# Test syntax
sudo nginx -t
# Expected output:
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# View active configurations
ls -la /etc/nginx/sites-enabled/
# Check specific config
sudo cat /etc/nginx/sites-enabled/boringdocs
Test Domain Resolution
# Check DNS
dig boringdocs.dev +short
# Should show: 123.45.67.89
# Test HTTP connection
curl -I http://boringdocs.dev
# Should show: HTTP/1.1 200 OK
# Test with specific Host header
curl -H "Host: boringdocs.dev" http://123.45.67.89
# Should return your app's response
Monitor Logs
# Watch Nginx access log
sudo tail -f /var/log/nginx/access.log
# Watch Nginx error log
sudo tail -f /var/log/nginx/error.log
# Watch your app's logs
# (location depends on how you're running your app)
Best Practices
Security
- Always use HTTPS - Set up SSL certificates with Let's Encrypt
- Hide Nginx version - Add to
/etc/nginx/nginx.conf
:server_tokens off;
- Set security headers - Add to your server blocks:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
Performance
-
Enable Gzip compression:
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml; -
Set up caching for static assets:
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
} -
Increase buffer sizes for better performance:
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
Organization
- Use descriptive config filenames:
boringdocs-portal
,boringdocs-api
, notsite1
,site2
- Comment your configurations:
# Main website - Next.js app on port 3000
server {
# ...
} - Keep one domain per file for easier management
- Use version control for your Nginx configs
Summary
Key takeaways:
- One IP for all apps: All your domains point to the same server IP address
- DNS configuration: Add A records pointing each domain to your server IP
- Nginx routes traffic: Uses
server_name
to match domains to applications - Port 80/443 for all: All server blocks listen on the same ports
- Each app on different port: Your applications run on different internal ports (3000, 4000, etc.)
The complete flow:
User types domain → DNS resolves to server IP → Request hits Nginx (port 80/443)
→ Nginx reads Host header → Matches server_name → Proxies to correct app port
→ App responds → Nginx returns response to user
Next steps:
- Set up SSL certificates with Let's Encrypt for HTTPS
- Configure automatic SSL renewal
- Set up monitoring for your applications
- Implement backup strategies for your configurations
- Consider using a process manager like PM2 for Node.js apps
You now understand how to configure multiple applications on a single server with different domains. The same IP address works for all apps because Nginx intelligently routes traffic based on the domain name in each request.