Chapter 2 — Web Server
The web server is the front door of your stack — it listens for incoming
HTTP requests and decides what to do with them. This chapter covers both
Apache and Nginx in full: installation, directory structure, configuration,
virtual hosts, and essential management commands. You only need to install
one; both are documented here so the chapter serves as a complete reference
regardless of which you choose.
Which should I pick? For Philip's Learning Blog — Apache.
The existing site uses .htaccess files and was built around
Apache conventions. For a new high-traffic site or a reverse proxy in front
of another service — Nginx. The rest of this chapter documents both equally.
Apache
⬡ Apache HTTP Server
Installation
$ sudo apt update
$ sudo apt install -y apache2
Reading package lists... Done
Setting up apache2 (2.4.57-2) ...
# Apache starts automatically after install. Verify:
$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled)
Active: active (running) since Tue 2026-06-17 10:00:00 BST
# Test from the server itself
$ curl -s http://localhost | grep -o "<title>.*</title>"
<title>Apache2 Debian Default Page: It works</title>
Open http://YOUR_SERVER_IP in a browser — you should see the
Apache2 Debian Default Page. That page lives at
/var/www/html/index.html and confirms Apache is serving files.
Directory Structure
/etc/apache2/
├── apache2.conf # Main config — global settings
├── ports.conf # Which ports Apache listens on (80, 443)
├── sites-available/ # All virtual host config files live here
│ ├── 000-default.conf # Default site (port 80)
│ └── default-ssl.conf # Default HTTPS site (disabled by default)
├── sites-enabled/ # Symlinks to active sites in sites-available
│ └── 000-default.conf # → ../sites-available/000-default.conf
├── mods-available/ # All available modules (.load + .conf pairs)
├── mods-enabled/ # Symlinks to active modules
└── conf-available/ # Additional config snippets
/var/www/html/ # Default document root
/var/log/apache2/
├── access.log # Every request logged here
└── error.log # Errors and warnings
sites-available vs sites-enabled: Apache uses a symlink
pattern — config files live in sites-available/ and a symlink
in sites-enabled/ activates them. Never edit files in
sites-enabled/ directly. Use a2ensite /
a2dissite to manage the symlinks automatically.
Useful Modules to Enable
Terminal — enable essential modules
# mod_rewrite — required for clean URLs and .htaccess rewrites
$ sudo a2enmod rewrite
# mod_headers — send custom HTTP headers (security headers, CORS)
$ sudo a2enmod headers
# mod_ssl — HTTPS support (needed for Chapter 5)
$ sudo a2enmod ssl
# mod_deflate — gzip compression for faster page loads
$ sudo a2enmod deflate
# Apply all module changes
$ sudo systemctl restart apache2
# Verify a module is enabled
$ apache2ctl -M | grep rewrite
rewrite_module (shared)
Configuring the Default Site
The default virtual host config controls what Apache serves when no other
virtual host matches. Edit it to point to your actual site files:
$ sudo vi /etc/apache2/sites-available/000-default.conf
# /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
ServerAdmin webmaster@osztromok.com
ServerName osztromok.com
ServerAlias www.osztromok.com
DocumentRoot /var/www/html
<Directory /var/www/html>
Options -Indexes +FollowSymLinks
AllowOverride All # Allows .htaccess to work
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Terminal — test config and reload
# Always test config syntax before reloading — catches typos
$ sudo apache2ctl configtest
Syntax OK
$ sudo systemctl reload apache2
Creating a Named Virtual Host
If you want to host multiple sites on one machine, or simply keep your
config tidy, create a dedicated virtual host file for each site:
# Create the document root for the new site
$ sudo mkdir -p /var/www/osztromok.com
$ sudo chown -R $USER:$USER /var/www/osztromok.com
# Create the virtual host config
$ sudo vi /etc/apache2/sites-available/osztromok.com.conf
# /etc/apache2/sites-available/osztromok.com.conf
<VirtualHost *:80>
ServerName osztromok.com
ServerAlias www.osztromok.com
DocumentRoot /var/www/osztromok.com
<Directory /var/www/osztromok.com>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/osztromok.com-error.log
CustomLog ${APACHE_LOG_DIR}/osztromok.com-access.log combined
</VirtualHost>
Terminal — enable the new site
# Enable the new site (creates the symlink in sites-enabled/)
$ sudo a2ensite osztromok.com.conf
Enabling site osztromok.com.
To activate the new configuration, you need to run:
systemctl reload apache2
# Disable the default site if this is your only site
$ sudo a2dissite 000-default.conf
$ sudo apache2ctl configtest && sudo systemctl reload apache2
Syntax OK
Apache Management Commands
| Task | Command |
| Start Apache | sudo systemctl start apache2 |
| Stop Apache | sudo systemctl stop apache2 |
| Restart (full restart) | sudo systemctl restart apache2 |
| Reload (no downtime) | sudo systemctl reload apache2 |
| Enable at boot | sudo systemctl enable apache2 |
| Test config syntax | sudo apache2ctl configtest |
| Enable a site | sudo a2ensite sitename.conf |
| Disable a site | sudo a2dissite sitename.conf |
| Enable a module | sudo a2enmod modulename |
| Disable a module | sudo a2dismod modulename |
| List enabled modules | apache2ctl -M |
| View error log (live) | sudo tail -f /var/log/apache2/error.log |
| View access log (live) | sudo tail -f /var/log/apache2/access.log |
| Check Apache version | apache2 -v |
Nginx
⬡ Nginx
Important: Apache and Nginx both listen on port 80 by
default. You cannot run both at the same time unless you change one of
them to a different port. Stop Apache first if it is running:
sudo systemctl stop apache2 && sudo systemctl disable apache2
Installation
$ sudo apt update
$ sudo apt install -y nginx
Setting up nginx (1.22.1-9) ...
# Nginx starts automatically. Verify:
$ sudo systemctl status nginx
● nginx.service - A high performance web server
Active: active (running)
$ curl -s http://localhost | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>
Directory Structure
/etc/nginx/
├── nginx.conf # Main config — worker processes, logging, includes
├── sites-available/ # All server block configs live here
│ └── default # Default server block
├── sites-enabled/ # Symlinks to active configs
│ └── default # → ../sites-available/default
├── conf.d/ # Additional config snippets (auto-loaded)
├── modules-available/
└── modules-enabled/
/var/www/html/ # Default document root (same as Apache)
/var/log/nginx/
├── access.log
└── error.log
No .htaccess in Nginx. Nginx does not read
.htaccess files at all — all configuration lives in the server
block files under /etc/nginx/sites-available/. Rewrite rules
that live in .htaccess for Apache need to be translated into
Nginx rewrite or try_files directives.
Configuring the Default Server Block
$ sudo vi /etc/nginx/sites-available/default
# /etc/nginx/sites-available/default
server {
listen 80;
listen [::]:80;
server_name osztromok.com www.osztromok.com;
root /var/www/html;
index index.php index.html index.htm;
# Try the request URI, then as a directory, then fall back to index.php
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Pass PHP requests to PHP-FPM (configured in Chapter 4)
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# Deny access to .htaccess files (they do nothing in Nginx
# but shouldn't be publicly readable)
location ~ /\.ht {
deny all;
}
error_log /var/log/nginx/osztromok.com-error.log;
access_log /var/log/nginx/osztromok.com-access.log;
}
Creating a Named Server Block
# Create a dedicated config file for the site
$ sudo vi /etc/nginx/sites-available/osztromok.com
# Enable it with a symlink
$ sudo ln -s /etc/nginx/sites-available/osztromok.com /etc/nginx/sites-enabled/
# Disable the default block if this is your only site
$ sudo rm /etc/nginx/sites-enabled/default
# Test config syntax — always do this before reloading
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
Nginx Management Commands
| Task | Command |
| Start Nginx | sudo systemctl start nginx |
| Stop Nginx | sudo systemctl stop nginx |
| Restart (full restart) | sudo systemctl restart nginx |
| Reload (no downtime) | sudo systemctl reload nginx |
| Enable at boot | sudo systemctl enable nginx |
| Test config syntax | sudo nginx -t |
| Enable a site | sudo ln -s /etc/nginx/sites-available/name /etc/nginx/sites-enabled/ |
| Disable a site | sudo rm /etc/nginx/sites-enabled/name |
| View error log (live) | sudo tail -f /var/log/nginx/error.log |
| View access log (live) | sudo tail -f /var/log/nginx/access.log |
| Check Nginx version | nginx -v |
Side-by-Side Quick Reference
| Task | Apache | Nginx |
| Test config | apache2ctl configtest | nginx -t |
| Reload config | systemctl reload apache2 | systemctl reload nginx |
| Enable a site | a2ensite name.conf | ln -s sites-available/name sites-enabled/ |
| Disable a site | a2dissite name.conf | rm sites-enabled/name |
| Enable a module | a2enmod modulename | (edit nginx.conf / install package) |
| Config location | /etc/apache2/ | /etc/nginx/ |
| Default doc root | /var/www/html/ | /var/www/html/ |
| Error log | /var/log/apache2/error.log | /var/log/nginx/error.log |
| .htaccess support | Yes (AllowOverride All) | No |
| PHP integration | mod_php or PHP-FPM | PHP-FPM only |
Placing your site files: Both web servers default to
/var/www/html/ as the document root. For a real site, either
copy or symlink your files there, or update the DocumentRoot /
root directive in the virtual host config to point to wherever
your files actually live. Ensure the web server user (www-data
on Debian) can read the files:
sudo chown -R www-data:www-data /var/www/html
Next: Chapter 3 — Database
Chapter 3 covers MySQL, MariaDB, and PostgreSQL — installation on Debian 12,
running the security hardening script, creating databases and users, and
connecting from PHP. All three are documented; for Philip's Learning Blog
the existing setup_database.sql script is ready to run straight
after installation.