Skip to main content

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:

  1. Cost-effective: One server instead of multiple servers
  2. Easier management: All apps in one place
  3. Resource sharing: Apps can share server resources efficiently
  4. 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:

  1. Browser asks DNS: "What IP address is boringdocs.dev?"
  2. DNS responds: "It's at 123.45.67.89"
  3. 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:

  1. Reads the Host header: "This request is for boringdocs.dev"
  2. Checks server blocks: Looks for a configuration matching boringdocs.dev
  3. Routes to correct app: Forwards the request to the right port (e.g., port 3000)
  4. 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):

TypeNameValueTTL
A@123.45.67.893600
Awww123.45.67.893600

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:

TypeNameValueTTL
Aapi123.45.67.893600
Ablog123.45.67.893600

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 80
  • listen [::]:80: Also accept IPv6 connections
  • server_name: The domain(s) this configuration handles
  • location /: 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:

HeaderPurposeWhy It Matters
HostOriginal domain nameYour app knows which domain was requested
X-Real-IPUser's actual IP addressFor logging and security
X-Forwarded-ForChain of proxy IPsTracks the request path
X-Forwarded-ProtoHTTP or HTTPSApp knows if connection was secure
UpgradeWebSocket upgrade requestsEnables real-time features
ConnectionConnection typeRequired 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:

  1. ln -s: Creates a symbolic link (like a shortcut) from sites-available to sites-enabled
  2. nginx -t: Tests configuration without applying it (safe to run)
  3. 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:

  1. Double-check that you added the A records in your DNS provider's control panel
  2. Wait for DNS propagation (usually 5-30 minutes, sometimes longer)
  3. 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 from domain2.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:

  1. Each domain appears in only one server block
  2. No typos in domain names
  3. 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

  1. Always use HTTPS - Set up SSL certificates with Let's Encrypt
  2. Hide Nginx version - Add to /etc/nginx/nginx.conf:
    server_tokens off;
  3. 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

  1. Enable Gzip compression:

    gzip on;
    gzip_vary on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
  2. Set up caching for static assets:

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    }
  3. Increase buffer sizes for better performance:

    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

Organization

  1. Use descriptive config filenames: boringdocs-portal, boringdocs-api, not site1, site2
  2. Comment your configurations:
    # Main website - Next.js app on port 3000
    server {
    # ...
    }
  3. Keep one domain per file for easier management
  4. Use version control for your Nginx configs

Summary

Key takeaways:

  1. One IP for all apps: All your domains point to the same server IP address
  2. DNS configuration: Add A records pointing each domain to your server IP
  3. Nginx routes traffic: Uses server_name to match domains to applications
  4. Port 80/443 for all: All server blocks listen on the same ports
  5. 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.