The Declarative Model
Web server configuration is declarative: you describe what you want to happen, not how to do it. You say "serve files from this directory" or "proxy requests to this backend"—the server figures out the implementation details.
This differs from writing application code (imperative), where you specify exact steps. Understanding this distinction helps you write effective configurations: focus on the outcome, let the server handle the mechanics.
Configuration vs. Code: When you configure Nginx, you're not programming—you're describing rules. The server matches requests against your rules and takes the corresponding action. Think of it as pattern matching, not step-by-step instructions.
Virtual Hosts: One Server, Many Domains
A single server can host multiple websites. When a request arrives, the server
checks the Host header to determine which site to serve:
Request: Server:
GET / HTTP/1.1 ┌─────────────────────────────┐
Host: shop.example.com ─────────► │ Which virtual host? │
│ │
│ shop.example.com → /shop/ │
│ blog.example.com → /blog/ │
│ api.example.com → proxy │
└─────────────────────────────┘
This is called name-based virtual hosting. A single IP address can serve hundreds of different websites, each with its own configuration.
# /etc/nginx/sites-available/shop.example.com
server {
listen 80;
server_name shop.example.com;
root /var/www/shop;
index index.html;
}
# /etc/nginx/sites-available/blog.example.com
server {
listen 80;
server_name blog.example.com;
root /var/www/blog;
index index.html;
}# /etc/apache2/sites-available/shop.example.com.conf
<VirtualHost *:80>
ServerName shop.example.com
DocumentRoot /var/www/shop
DirectoryIndex index.html
</VirtualHost>
# /etc/apache2/sites-available/blog.example.com.conf
<VirtualHost *:80>
ServerName blog.example.com
DocumentRoot /var/www/blog
DirectoryIndex index.html
</VirtualHost>import { createServer } from 'node:http';
import { join } from 'node:path';
import { createReadStream } from 'node:fs';
const sites = {
'shop.example.com': '/var/www/shop',
'blog.example.com': '/var/www/blog'
};
const server = createServer((req, res) => {
const host = req.headers.host;
const root = sites[host];
if (!root) {
res.writeHead(404);
res.end('Site not found');
return;
}
const path = req.url === '/' ? '/index.html' : req.url;
const file = join(root, path);
createReadStream(file).pipe(res);
});
server.listen(80);Notice how Nginx and Apache configurations are purely declarative—no procedural logic. Node.js, being a programming environment, requires you to implement the virtual host logic yourself.
Document Root and Directory Structure
The document root is the directory on the filesystem that maps
to the root URL (/) of your site. When a client requests
/images/logo.png, the server looks for
{document_root}/images/logo.png.
# Document root mapping
Request: GET /css/style.css
Document root: /var/www/mysite
Filesystem path: /var/www/mysite/css/style.cssCommon Directory Conventions
| Path | Location | Notes |
|---|---|---|
/var/www/ |
Default web root (Debian/Ubuntu) | Standard location for all sites |
/usr/share/nginx/html/ |
Nginx default | Used for the default welcome page |
/home/user/public_html/ |
User home directories | For shared hosting setups |
/srv/www/ |
FHS-compliant location | Preferred by some distributions |
Typical Site Structure
/var/www/mysite/
├── index.html # Default document
├── about.html
├── css/
│ └── style.css
├── js/
│ └── main.js
├── images/
│ ├── logo.png
│ └── hero.jpg
└── downloads/
└── brochure.pdfMIME Types
When serving files, the server must tell the browser what type of content it's
sending. This is the Content-Type header, based on MIME types
(Multipurpose Internet Mail Extensions).
| Extension | MIME Type | Category |
|---|---|---|
.html |
text/html |
Document |
.css |
text/css |
Stylesheet |
.js |
application/javascript |
Script |
.json |
application/json |
Data |
.png |
image/png |
Image |
.jpg |
image/jpeg |
Image |
.svg |
image/svg+xml |
Image |
.woff2 |
font/woff2 |
Font |
.pdf |
application/pdf |
Document |
Servers typically have a default MIME types file (/etc/mime.types)
that maps extensions to content types. You can add custom mappings:
# Include standard MIME types
include /etc/nginx/mime.types;
# Add custom types
types {
application/wasm wasm;
text/markdown md;
}
# Default for unknown extensions
default_type application/octet-stream;# Add custom MIME types
AddType application/wasm .wasm
AddType text/markdown .md
# Default for unknown extensions
DefaultType application/octet-stream
Why MIME types matter: Browsers use the Content-Type header
to decide how to handle content. A JavaScript file served as text/plain
won't execute. An SVG served without image/svg+xml may not render.
Incorrect MIME types are a common source of subtle bugs.
Index Files and Directory Behavior
When a request targets a directory (like /products/), the server
needs to decide what to return. Options include:
- Serve an index file — Look for
index.html,index.php, etc. - Show a directory listing — Display the contents of the directory
- Return an error — 403 Forbidden or 404 Not Found
server {
# Try these files in order when a directory is requested
index index.html index.htm;
location / {
# Try: exact file → directory with index → 404
try_files $uri $uri/ =404;
}
# Enable directory listing for a specific path
location /downloads/ {
autoindex on;
autoindex_exact_size off; # Show human-readable sizes
}
}# Set default index files
DirectoryIndex index.html index.htm
# Disable directory listing globally
<Directory /var/www>
Options -Indexes
</Directory>
# Enable for specific directory
<Directory /var/www/mysite/downloads>
Options +Indexes
IndexOptions FancyIndexing
</Directory>Configuration File Organization
Both Nginx and Apache support splitting configuration across multiple files. This keeps configurations manageable and allows enabling/disabling sites easily.
Nginx Structure
/etc/nginx/
├── nginx.conf # Main configuration
├── mime.types # MIME type mappings
├── sites-available/ # All site configs
│ ├── default
│ ├── shop.example.com
│ └── blog.example.com
├── sites-enabled/ # Symlinks to active sites
│ ├── default -> ../sites-available/default
│ └── shop.example.com -> ../sites-available/shop.example.com
└── snippets/ # Reusable config fragments
└── ssl-params.confApache Structure
/etc/apache2/
├── apache2.conf # Main configuration
├── ports.conf # Port definitions
├── sites-available/ # All site configs
│ ├── 000-default.conf
│ └── shop.example.com.conf
├── sites-enabled/ # Symlinks to active sites
│ └── 000-default.conf -> ../sites-available/000-default.conf
├── mods-available/ # Available modules
└── mods-enabled/ # Active modulesEnable and disable sites using command-line tools:
# Nginx
sudo ln -s /etc/nginx/sites-available/shop.example.com /etc/nginx/sites-enabled/
sudo nginx -t # Test configuration
sudo systemctl reload nginx
# Apache
sudo a2ensite shop.example.com.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest # Test configuration
sudo systemctl reload apache2Testing Configuration Changes
Always test configuration changes before reloading:
# Nginx: test syntax
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# Apache: test syntax
sudo apache2ctl configtest
# Syntax OK
# Reload (graceful - doesn't drop connections)
sudo systemctl reload nginx
sudo systemctl reload apache2
Reload vs. Restart: reload tells the server to
re-read its configuration without dropping existing connections. restart
stops and starts the server, terminating active connections. Always prefer
reload in production.
What's Next
Now that you understand basic configuration concepts, the next tutorial covers URL handling: how servers match requests to locations, perform redirects, and rewrite URLs.