Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

2.2 Server Blocks & Virtual Hosts

One IP, one port, many sites: the exact algorithm NGINX uses to pick a server{}, with every claim verified against a running server.

Levels: Beginner ~35 min read

Basic

What you will learn

  1. What a server{} block really is: an identity ( listen + server_name) plus a policy.

  2. Name-based virtual hosting — how the Host header drives selection.

  3. The complete server-selection algorithm, in order, with live evidence for each step.

  4. default_server — your safety net for unknown, absent, and hostile Host headers.

  5. Running many ports, and mixing listen variants.


1. What a server block is

nginx
server {
    listen       8200;                  # WHERE: address/port this server answers
    server_name  alpha.example.test;    # WHO: which Host values claim it
    …                            # WHAT: root, locations, headers, logs…
}

A server{} is the answer to three questions:

  • Where — the sockets it may be selected on ( listen). Multiple servers may share a socket; that sharing is the whole point.

  • Who — the Host: request header values that select it ( server_name).

  • What — everything else in the block: the policy applied to requests that land here.

The example config runs five servers on one socket ( 127.0.0.1:8200) plus a sixth on :8201 (via conf.d/). Here is the selection matrix, captured live — burn this table into memory:

bash
$ for h in alpha.example.test beta.example.test random.example.test \
           app-acme.apps.test unknown.host.xyz; do
    printf '%-22s -> %s\n' "$h" "$(curl -s -H "Host: $h" http://127.0.0.1:8200/ | head -1)"
  done
alpha.example.test     -> <!DOCTYPE html>            (alpha: serves www/main)
beta.example.test       -> <!DOCTYPE html>            (beta: serves www/spa)
random.example.test    -> matched: wildcard .example.test (host=random.example.test)
app-acme.apps.test     -> matched: regex vhost, tenant=acme
unknown.host.xyz       -> matched: default_server catch-all (host=unknown.host.xyz)

Same IP. Same port. Five different answers. That is name-based virtual hosting, and the Host header is the entire steering signal.

2. The server-selection algorithm

When a request arrives on a listening socket, NGINX runs this exact sequence:

text
1. Filter by socket:      only servers whose listen matches the connection
2. Read the Host header:  strip the port (":8200"), lowercase it
3. Try EXACT names:       server_name alpha.example.test  — first hit wins
4. Try LEADING wildcards: *.example.test — LONGEST match wins
   (".example.test" is shorthand for example.test + *.example.test)
5. Try TRAILING wildcards: www.example.*  — LONGEST match wins
6. Try REGEX names:       ~^app-(?<tenant>…)$ — FIRST in config order wins
7. Fall back:             the default_server for that socket
                          (the one marked, or the FIRST server on that port)

Numbers 3–7 are strictly ordered: an exact match anywhere beats every wildcard; any wildcard beats every regex; regexes only see hosts the wildcards declined. Two of these orderings are already visible in the capture above:

  • alpha.example.test → the exact server, even though the wildcard .example.test also matches it. Exactness wins.

  • random.example.test → the wildcard, even though a regex could have been written to match. Wildcards are consulted first, period.

The capture trick when you do use regex names

nginx
server {
    listen      8200;
    server_name ~^app-(?<tenant>[a-z0-9]+)\.apps\.test$;
    return 200 "matched: regex vhost, tenant=$tenant\n";
}

Named captures ( ?<tenant>) become variables usable anywhere in the server — $tenant here. That single feature is why regex names survive.

bash
$ curl -s -H "Host: app-acme.apps.test" http://127.0.0.1:8200/
matched: regex vhost, tenant=acme

3. default_server — the safety net

nginx
server {
    listen       8200 default_server;
    server_name  _;                # "_" is an invalid domain: pure convention
    …
}

Whatever request arrives on :8200 that no other server claims — unknown name, a raw IP as Host, a scanner sending garbage — lands here. Three professional uses:

  1. A polite landing page (a common choice): explain the site, don't leak your real vhost's existence.

  2. A black hole: return 444; — NGINX's proprietary "close connection without response" code. Scanners get silence. (Module 7 builds a full IP-reputation version of this.)

  3. A redirect farm: catch example.test + typos and 301 them to www.example.test.

Also note: default_server is per socket ( listen group), not global — a server listening on 8200 and another on 8201 each want their own.

4. More sockets, more ports

The identity rule worth stating out loud: a server is selectable only on the sockets it listens on. The gamma vhost proves it — it lives in conf.d/extra-vhost.conf with listen 8201 and simply cannot be reached through 8200:

bash
$ curl -s -H "Host: gamma.example.test" http://127.0.0.1:8201/from
served by the conf.d include, port 8201

listen accepts richer forms that you'll use in production:

nginx
listen 127.0.0.1:8080;         # specific address (localhost only)
listen 443 ssl;                # protocol flags (Module 6)
listen [::]:80;                # IPv6 (all v6 addresses)
listen 8080 reuseport;         # per-worker accept queues (Module 9)
listen 443 quic reuseport;     # HTTP/3 (Module 9)
listen 80 default_server;      # claim the fallback slot
listen 8080 backlog=2048;      # kernel accept-queue depth (Module 9)

And server_name accepts lists and both wildcard shapes:

nginx
server_name  example.test  www.example.test  example.test.;   # exact list
server_name  *.example.test;        # leading wildcard (one level)
server_name  mail.*;                # trailing wildcard
server_name  .example.test;         # domain + ONE-level wildcard, combined
server_name  ~^app-(?<t>[a-z0-9]+)\.apps\.test$;   # regex (last resort)

5. How this maps onto the request walk

Putting Lessons 2.1–2.2 together, a request's first steps through the config tree are now fully specified:

text
connection accepted on a listening socket
  └─ choose ONE server{}            ← this lesson
       └─ choose ONE location{}     ← next lesson
            └─ handler + inherited directives

Everything "above" the chosen server (http-level directives) still applies through inheritance (Lesson 2.5). Everything below — the locations — is next.

Try it yourself

Against a running instance, every experiment is a one-liner:

  1. Reproduce the selection matrix (§1) yourself, then add your own cases: Host: ALPHA.EXAMPLE.TEST (case-insensitivity), Host: a.b.example.test (multi-level — wildcard .example.test declines it!), Host: app-42.apps.test.

  2. Watch the default server catch strays. Send a request with no Host at all (HTTP/1.0):

    bash
    curl -s --http1.0 http://127.0.0.1:8200/ | head -1
    
  3. Prove exact beats wildcard. alpha.example.test matches both alpha (exact) and .example.test (wildcard) — confirm which answered via the X-Server response header:

    bash
    curl -sD - -o /dev/null -H 'Host: alpha.example.test' http://127.0.0.1:8200/ | grep X-Server
    
  4. Inspect, don’t trust. Ask the instance what servers it has:

    bash
    nginx -T | grep server_name
    

Q&A

Q1. Requests for deep.sub.example.test hit your default server, but sub.example.test works. Why?

Answer

.example.test / *.example.test match exactly one level of subdomain. deep.sub.… has two. Either add *.sub.example.test, a regex name, or a dedicated server — in that order of preference.

Q2. Two servers both declare listen 8200 default_server;. What happens?

Answer

Startup fails: [emerg] a duplicate default server for 127.0.0.1:8200. The fallback role is exclusive per socket.

Q3. Why is server_name _; conventional rather than magical?

Answer

_ is simply an invalid hostname that will never match a real client request through steps 3–6; the server is therefore reachable only as the default. Any impossible name ( __catch__, null) works identically.

Q4. Your TLS site needs to answer both example.test and *.example.test with one certificate-covered server. Minimal server_name?

Answer

server_name .example.test; — the dot shorthand covers the bare domain and one level of subdomains. (Certificate SANs must cover the same set — Module 6 ties these together.)

Takeaways

  • A server{} = where ( listen) + who ( server_name) + what (policy).

  • Selection order is fixed: exact → longest leading wildcard → longest trailing wildcard → first regex in config order → default_server.

  • default_server is per-socket, must be explicit, and is where unknown, dotted, and hostile Hosts land — design it deliberately.

  • Regex server names are a last resort whose superpower is named captures ( $tenant) — keep at most one pattern-shaped server.

  • One server per port group can be "moved" by reordering includes — mark your defaults and your config becomes order-independent.

Was this page helpful?

Mark this learning resource complete to track your learning progress.