Level 0 — Fundamental Concepts
Before touching a server, you need a mental model of what a server is and what happens between a user typing a URL and your code running. Everything later in this guide is a detail hanging off this chapter.
What is a server?
A server is not a special kind of computer. It is a role. Any computer running a program that waits for incoming requests and answers them is a server. Your laptop running pnpm dev is a server.
What makes a production server different is the environment around it:
- It runs 24/7 in a datacenter with redundant power and network.
- It has a static public IP address, so the world can find it.
- It has no monitor or keyboard — you interact with it only over the network, via SSH.
- It runs no desktop, no browser, no GUI. Just a kernel, system services, and your apps.
What is a VPS?
A VPS (Virtual Private Server) is a slice of a big physical machine, given to you as if it were a whole computer.
A provider like Hetzner owns physical servers with, say, 128 CPU cores and 512 GB of RAM. A hypervisor (KVM on most Linux hosts) divides that machine into many isolated virtual machines. You rent one. You get:
- A guaranteed slice of CPU and RAM
- Your own disk
- Your own public IP
- Full root access — you can install anything, change any setting
You do not get: control over the physical hardware, or protection from the host failing.
VPS vs the alternatives
| Option | You manage | Provider manages | Best for |
|---|---|---|---|
| Shared hosting | Files only | Everything | Static sites, WordPress |
| VPS | OS, packages, security, app | Hardware, network | Full control, small–medium apps |
| Dedicated server | OS, packages, security, app | Hardware only | High, sustained load |
| PaaS (Vercel, Railway) | App code only | Everything else | Speed of setup, hands-off ops |
| Kubernetes | Everything + orchestration | Hardware | Many services, many teams |
WHEN A VPS IS THE RIGHT CHOICE
A single VPS comfortably serves a Nuxt + NestJS app with thousands of daily users for €5–20/month. Choose it when you want predictable cost and full control. Choose PaaS when your time is worth more than the savings, and Kubernetes only when you genuinely have many services and people.
What is Linux?
Linux is the kernel — the core program that talks to hardware, schedules processes, manages memory, and controls access to files and the network. It is not, by itself, a usable system.
A Linux distribution bundles the kernel with everything else: a shell, core utilities, a package manager, an init system, and thousands of installable packages.
Key consequences of the Linux model, which explain a lot of later behaviour:
- Everything is a file. Disks, network sockets, and even running processes are exposed as files (
/dev/sda,/proc/1234). This is why permissions matter so much. - Permissions are enforced by the kernel. A process running as user
deployphysically cannot read a file that onlyrootcan read. - Processes are isolated but share the machine. One runaway process can exhaust RAM and get everything else killed.
What is Ubuntu?
Ubuntu is a Linux distribution built on Debian, maintained by Canonical. It is the default choice for servers because it has the largest body of documentation and the widest package support.
LTS means Long Term Support. Ubuntu 24.04 LTS ("Noble Numbat") was released in April 2024 and receives free security updates until April 2029 (extendable to 2036 with Ubuntu Pro).
ALWAYS USE LTS IN PRODUCTION
Non-LTS releases (24.10, 25.04) are supported for only 9 months. When support ends, you stop getting security patches. Never run a non-LTS release on a production server.
Version numbering is YY.MM of release: 22.04 = April 2022, 24.04 = April 2024. A new LTS lands every two years, in April.
What is a process?
A process is a running instance of a program. When you start your NestJS API, the kernel:
- Loads the
nodebinary into memory - Assigns it a PID (Process ID) — a unique number
- Gives it its own memory space
- Records which user it runs as
- Schedules it onto a CPU core
Every process has:
| Attribute | Meaning | How to see it |
|---|---|---|
| PID | Unique process ID | ps aux |
| PPID | Parent process ID | ps -ef |
| User | Whose permissions it runs with | ps aux (first column) |
| Working directory | Where relative paths resolve | pwdx <pid> |
| Environment | Its env vars | cat /proc/<pid>/environ |
| Open files | Files and sockets it holds | lsof -p <pid> |
PID 1 is special: it is systemd on Ubuntu, the first process the kernel starts, and the ancestor of everything else. If a process's parent dies, PID 1 adopts it.
WHY THIS MATTERS FOR DEPLOYMENT
When your SSH session ends, the shell sends SIGHUP to its child processes. If you started your app with pnpm start in that shell, your app dies with the session. This is exactly the problem PM2 and systemd solve — see Level 13.
Signals
Processes are controlled by signals:
| Signal | Number | Meaning | Can be caught? |
|---|---|---|---|
SIGTERM | 15 | "Please shut down cleanly" | Yes — the graceful path |
SIGINT | 2 | Ctrl+C | Yes |
SIGHUP | 1 | Terminal closed / reload config | Yes |
SIGKILL | 9 | Die now | No — kernel kills it instantly |
SIGKILL gives your process no chance to finish in-flight HTTP requests, flush logs, or close database connections. Always try SIGTERM first.
What is a port?
An IP address identifies a machine. A port identifies a program on that machine.
Ports are 16-bit numbers: 1–65535. A socket is the pair (IP address, port). Only one process can listen on a given (IP, port, protocol) combination at a time — which is why you get EADDRINUSE when you start two apps on port 3000.
| Range | Name | Notes |
|---|---|---|
| 0–1023 | Well-known / privileged | Requires root (or CAP_NET_BIND_SERVICE) to bind |
| 1024–49151 | Registered | Where application ports live (3000, 5432, 6379) |
| 49152–65535 | Ephemeral | Assigned automatically to outgoing connections |
Common ports you will meet:
| Port | Service | Public? |
|---|---|---|
| 22 | SSH | Yes (restricted) |
| 80 | HTTP | Yes |
| 443 | HTTPS | Yes |
| 3000 | Nuxt (our convention) | No — localhost only |
| 3001 | NestJS API (our convention) | No — localhost only |
| 5432 | PostgreSQL | Never |
| 6379 | Redis | Never |
THE MOST COMMON SERIOUS MISTAKE
Binding PostgreSQL or Redis to 0.0.0.0 exposes them to the entire internet. Automated scanners find an open 5432 or 6379 within minutes. Redis with no password is trivially converted into a crypto-miner install. Bind internal services to 127.0.0.1. See Level 5.
Binding: 127.0.0.1 vs 0.0.0.0
This one distinction prevents most accidental exposure:
| Bind address | Reachable from | Use for |
|---|---|---|
127.0.0.1 (loopback) | Only this machine | App servers, databases, Redis |
0.0.0.0 (all interfaces) | Anywhere the firewall allows | Nginx only |
10.0.0.5 (private IP) | Other servers on the private network | Multi-server setups |
Your Nuxt and NestJS processes should listen on 127.0.0.1. Nginx listens on 0.0.0.0:443 and forwards inward. Even if the firewall fails, a 127.0.0.1-bound service is unreachable from outside.
What is an IP address?
An IP address is the postal address of a machine on a network.
IPv4 vs IPv6
| IPv4 | IPv6 | |
|---|---|---|
| Format | 203.0.113.10 | 2a01:4f8:c17:1234::1 |
| Size | 32 bits | 128 bits |
| Total addresses | ~4.3 billion (exhausted) | 3.4 × 10³⁸ |
| Written as | 4 decimal octets | 8 hex groups, :: collapses zeros |
| DNS record | A | AAAA |
Both are in active use. Hetzner gives every VPS one IPv4 address and a whole IPv6 /64 block. Configure both an A and an AAAA record so IPv6-only clients (increasingly common on mobile networks) can reach you.
A CLASSIC BUG
If you publish an AAAA record but your firewall or Nginx only listens on IPv4, IPv6 users get timeouts while everything looks fine to you. UFW handles IPv6 when IPV6=yes in /etc/default/ufw (the default on 24.04), and Nginx needs listen [::]:443 ssl; alongside listen 443 ssl;.
Public vs private IP
| Public IP | Private IP | |
|---|---|---|
| Routable on the internet | Yes | No |
| Example | 203.0.113.10 | 10.0.0.5, 192.168.1.20, 172.17.0.2 |
| Assigned by | Your provider | Your router / cloud private network / Docker |
| Cost | Usually paid | Free |
Reserved private ranges (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
If you later run a second server for the database, connect them over the provider's private network (Hetzner Cloud Networks) using private IPs. Traffic never touches the public internet, and you can firewall the database to accept connections only from 10.0.0.0/8.
TCP vs UDP
Both sit on top of IP. IP moves packets; TCP and UDP decide what guarantees you get.
| TCP | UDP | |
|---|---|---|
| Connection | Yes — handshake first | No — fire and forget |
| Ordering guaranteed | Yes | No |
| Delivery guaranteed | Yes — retransmits | No |
| Speed | Slower (overhead) | Faster |
| Used by | HTTP, HTTPS, SSH, PostgreSQL, Redis | DNS, QUIC/HTTP3, video streaming, games |
Everything in this guide uses TCP except DNS lookups (UDP port 53) and, if you enable it, HTTP/3 (QUIC over UDP 443).
The TCP handshake
This explains two error messages you will see constantly:
- Connection refused — the packet reached the machine, but nothing was listening on that port. The server actively said "no". Fast failure.
- Connection timed out — the packet vanished. A firewall dropped it silently, or the IP is wrong. Slow failure (~30s+).
Learn to distinguish these; they point at completely different causes.
DNS basics
DNS (Domain Name System) translates human names into IP addresses. It is a distributed, hierarchical, heavily-cached database.
The key practical consequence is caching. When you change a DNS record, the old value stays in caches around the world until its TTL (Time To Live) expires. A record with TTL 86400 can take a full day to fully propagate. Full details in Level 15.
HTTP vs HTTPS
HTTP is the request/response protocol of the web. It is plain text — anyone between the user and your server (their ISP, café WiFi, a compromised router) can read and modify it.
HTTPS is HTTP wrapped in TLS. TLS provides three things:
| Property | Meaning | Without it |
|---|---|---|
| Encryption | Nobody in the middle can read the traffic | Passwords and session cookies are in plain view |
| Integrity | Nobody can modify it undetected | ISPs inject ads; attackers inject scripts |
| Authentication | You are talking to the real example.com | A DNS hijack sends users to a lookalike |
A certificate is a file signed by a Certificate Authority (CA) that binds your domain name to a public key. Browsers trust ~150 CAs by default. Let's Encrypt is a free, automated CA — covered in Level 16.
HTTPS IS NOT OPTIONAL IN 2026
Browsers mark HTTP pages as "Not secure". Service workers, geolocation, clipboard access, and HTTP/2 all require HTTPS. Secure cookies do not work. Search engines rank you lower. There is no cost argument left — Let's Encrypt is free and automated.
Reverse proxy
A forward proxy sits in front of clients. A reverse proxy sits in front of servers.
Why not just point the domain straight at Node?
| Problem | How the reverse proxy solves it |
|---|---|
| Node would need to bind port 443 | Nginx binds privileged ports; Node stays on 3000 as a normal user |
| One IP, many apps | Nginx routes by Host header to different backends |
| TLS in every app | TLS terminates once, in Nginx |
| Node is slow at static files | Nginx serves them directly from disk with sendfile |
| No compression / caching layer | Nginx does gzip, brotli, caching, rate limiting |
| Node crashes → site is down | Nginx returns a controlled 502 and recovers instantly when the app returns |
| Slow-client attacks tie up Node | Nginx buffers requests and responses |
THIS IS THE SINGLE MOST IMPORTANT ARCHITECTURAL DECISION
Every production Node deployment puts a reverse proxy in front. Nginx (or Caddy, or Traefik) is not optional complexity — it is the boundary between the hostile internet and your application.
Application server
Your application server is the process that runs your code: node .output/server/index.mjs for Nuxt, node dist/main.js for NestJS.
Rules for production:
- Bind to
127.0.0.1, never0.0.0.0. - Run as a non-root user (
deploy). - Run under a process manager so it restarts on crash and on reboot.
- Read configuration from environment variables, not hard-coded values.
- Handle
SIGTERMby finishing in-flight requests, then exiting.
Database server
A database server is a long-running process that owns your data on disk and answers queries over a socket.
| PostgreSQL | Redis | |
|---|---|---|
| Type | Relational (SQL) | Key-value, in-memory |
| Durability | ACID, crash-safe by design | Configurable, weaker by default |
| Stores | Your source of truth | Caches, sessions, queues, pub/sub |
| Port | 5432 | 6379 |
| Losing it means | Disaster — restore from backup | Annoyance — cold caches, dropped sessions |
Both must be reachable only from your application, never from the internet.
What happens when a user opens https://app.example.com
This is the whole system in one sequence. Every step maps to a chapter in this guide.
Step by step:
- DNS resolution — the browser asks for the IP of
app.example.com. Answer:203.0.113.10. (Level 15) - TCP handshake — the browser opens a TCP connection to port 443. Your firewall must allow this. (Level 5)
- TLS handshake — the browser and Nginx negotiate encryption. Nginx presents the Let's Encrypt certificate, selected by SNI (the domain the browser asked for). (Level 16)
- HTTP request — now encrypted,
GET /arrives with aHost: app.example.comheader. - Nginx routing — Nginx matches
server_name app.example.comand proxies to127.0.0.1:3000. (Level 14) - Nuxt SSR — the Nuxt server renders the page, calling your API for data.
- NestJS — checks Redis, falls through to PostgreSQL, caches the result. (Levels 9, 10)
- Response travels back through Nginx, encrypted, to the browser.
- PM2 kept steps 6 and 7 alive the whole time and will restart them if they crash. (Level 13)
When something breaks in production, you debug by walking this chain and asking at each hop: did the request get this far? Chapter 26 is built around exactly that method.
Production Checklist — Level 0
- [ ] I can explain the difference between "connection refused" and "connection timed out"
- [ ] I understand why app servers bind to
127.0.0.1and only Nginx binds to0.0.0.0 - [ ] I can name the three guarantees TLS provides
- [ ] I understand why PostgreSQL and Redis must never be publicly reachable
- [ ] I can trace the full path of a request from browser to database and back
- [ ] I know that only LTS Ubuntu releases belong on a production server
- [ ] I understand that a process started in an SSH session dies when the session ends