Nginx and Apache Web Servers in Production
Exit

Lesson 1 of 11 · Fundamentals

Anatomy of a production web server — processes, files and the configuration actually loaded

30 min Debian 12

Objective

Know how nginx and Apache handle connections, where each configuration file lives, and find the configuration actually loaded before you touch anything.

Key points

  • nginx runs with one master process (reads the configuration, opens the ports) and workers that each handle thousands of connections in parallel.
  • Apache 2.4 uses an MPM: event (processes + threads, the modern choice) or prefork (one process per connection, only for the old mod_php).
  • The file you edit is not always the one that runs: include directives, links in sites-enabled and distribution defaults all add up.
  • nginx -T displays the whole loaded configuration, includes and all; apachectl -S lists the virtual hosts that are really active.
  • Always test (nginx -t, apachectl configtest), then reload (reload). Do not restart.

1. What happens when a client connects

                 ┌───────────────────────────────┐
navigateur ───►  │ processus maître (root)        │  lit nginx.conf, ouvre 80/443
                 │   ├─ worker 1 (www-data)        │  ← accepte et traite les connexions
                 │   ├─ worker 2 (www-data)        │
                 │   └─ worker N (= nb de cœurs)   │
                 └───────────────────────────────┘

(The browser connects; the master process (root) reads nginx.conf and opens 80/443; the workers (www-data) accept and handle the connections; there are N workers, one per CPU core.)

An nginx worker never blocks on a slow client: it moves on to the next one and comes back when data arrives. This is why a small nginx server can hold tens of thousands of open connections.

ps -o pid,user,cmd -C nginx
#    PID USER     CMD
#    812 root     nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
#    813 www-data nginx: worker process
#    814 www-data nginx: worker process

Apache with the event MPM looks like this: a few processes, each with dozens of threads, and a "listener" thread that keeps idle keep-alive connections without tying up a worker thread.

apachectl -V | grep -i mpm         # Server MPM:     event
apachectl -M | grep mpm            # mpm_event_module (shared)

2. Where the files are

Item nginx (Debian/Ubuntu) nginx (Rocky) Apache (Debian/Ubuntu) Apache (Rocky)
Main file /etc/nginx/nginx.conf same /etc/apache2/apache2.conf /etc/httpd/conf/httpd.conf
Sites sites-available/ + links in sites-enabled/ conf.d/*.conf sites-available/ + a2ensite conf.d/*.conf
Reusable pieces snippets/ default.d/ conf-available/ + a2enconf conf.d/
Logs /var/log/nginx/ same /var/log/apache2/ /var/log/httpd/
Worker user www-data nginx www-data apache

On Debian and Ubuntu, nginx.conf contains these two lines. They alone decide which sites are loaded.

include /etc/nginx/conf.d/*.conf;      # only files ending in .conf
include /etc/nginx/sites-enabled/*;    # everything in the directory

Direct consequence: a file ancien.conf.bak in conf.d/ is not loaded, but a file ancien.bak in sites-enabled/ is.

3. Read the configuration actually loaded

nginx -t                                   # syntax OK? (reads every include)
nginx -T | less                            # everything, file by file (# configuration file …)
nginx -T 2>/dev/null | grep -n 'server_name'   # which names are served, and in which file
nginx -T 2>/dev/null | grep -E '^# configuration file'   # list of files read

apachectl configtest                       # Syntax OK
apachectl -S                               # virtual hosts, port by port, and the default site
apachectl -t -D DUMP_INCLUDES              # included files

Example output of apachectl -S:

VirtualHost configuration:
*:80                   is a NameVirtualHost
         default server www.masterlinux.lab (/etc/apache2/sites-enabled/000-default.conf:1)
         port 80 namevhost www.masterlinux.lab (/etc/apache2/sites-enabled/000-default.conf:1)
         port 80 namevhost intranet.masterlinux.lab (/etc/apache2/sites-enabled/intranet.conf:1)

4. Test, reload, do not cut

nginx -t && systemctl reload nginx          # the master rereads, starts new workers,
                                            # the old ones finish their requests and then stop
apachectl configtest && systemctl reload apache2   # (httpd on Rocky): "graceful" restart

restart cuts every connection in progress. reload does not. And if the new configuration is wrong, reload keeps the old one running. This is why you test before.

5. Real case: "I fixed it, but nothing changes"

At the MasterLinux office in Fès, a technician fixes client_max_body_size in /etc/nginx/sites-available/intranet… in a copy, intranet.old, because the link in sites-enabled pointed to the other file. One hour lost. The reflex that would have solved it in 10 seconds:

nginx -T 2>/dev/null | grep -n -B2 -A2 client_max_body_size
ls -l /etc/nginx/sites-enabled/

Common pitfalls

  • Editing the wrong file: always check what is loaded with nginx -T or apachectl -S.
  • Leaving backups in sites-enabled/: site.bak is loaded there and creates duplicate server_name entries.
  • restart instead of reload: it cuts downloads and WebSocket connections in progress.
  • Reloading without testing: on Apache, a syntax error can stop the service from starting at the next boot.

Lab

nginx is not installed on your lab machine: you work directly on a prepared /etc/nginx tree, as if you were reading the configuration of an unknown server. You will:

  1. find the file that serves the shop (boutique);
  2. disable a site cleanly without losing its configuration;
  3. rebuild by hand what nginx -T would display, following the include rules.

Quiz

  1. Which nginx process opens ports 80 and 443, and which user do the workers run as on Debian?
  2. Is a file test.conf.old in conf.d/ loaded? And test.old in sites-enabled/?
  3. Which command displays the whole nginx configuration actually loaded?
  4. What is the difference between reload and restart for connected clients?
  5. Which Apache MPM should you prefer with PHP-FPM?
Answers
  1. The master process (root); the workers run as www-data.
  2. No for conf.d/test.conf.old (the pattern is *.conf); yes for sites-enabled/test.old (the pattern is *).
  3. nginx -T.
  4. reload lets requests in progress finish with the old workers; restart cuts everything.
  5. event.

Practical exercises

To do on Debian 12

Do this lab on your own machine

The terminal is yours: a Linux VM (VirtualBox, VMware, Hyper-V, WSL…) or Docker/Podman. The ml tool prepares the exercise and checks your work; the lesson is validated here automatically.

Create a free account to get your personal command and have your labs checked automatically.

Create my account

Tasks

  1. Find the file that declares server_name boutique.masterlinux.lab and write its full path (in sites-available or sites-enabled), alone on one line, in /root/reponse-1.txt.
    Show a hintgrep -rl 'boutique.masterlinux.lab' /etc/nginx
    Show the solution
    grep -rl 'server_name boutique.masterlinux.lab' /etc/nginx/sites-available > /root/reponse-1.txt

    Check : grep -Eqx '/etc/nginx/sites-(available|enabled)/boutique' /root/reponse-1.txt

  2. The demo site must no longer be loaded: disable it by removing only its link in sites-enabled (the file in sites-available must stay).
    Show a hintrm /etc/nginx/sites-enabled/demo — it is a symbolic link.
    Show the solution
    rm -f /etc/nginx/sites-enabled/demo

    Check : test ! -e /etc/nginx/sites-enabled/demo && test -f /etc/nginx/sites-available/demo && test -L /etc/nginx/sites-enabled/boutique

  3. Rebuild by hand what nginx -T would display: write to /root/config-complete.txt the content of nginx.conf followed by all the files actually included (conf.d/*.conf and sites-enabled/*). The file ancien.conf.bak must not appear in it.
    Show a hintcat /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf /etc/nginx/sites-enabled/* > /root/config-complete.txt
    Show the solution
    cat /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf /etc/nginx/sites-enabled/* > /root/config-complete.txt

    Check : f=/root/config-complete.txt; test -f $f && grep -q 'worker_connections 768' $f && grep -q 'client_max_body_size 20m' $f && grep -q 'server_name boutique.masterlinux.lab' $f && ! grep -q 'ancien.masterlinux.lab' $f && ! grep -q 'demo.masterlinux.lab' $f