What is a Web Server?
A web server is a program that listens for requests coming from browsers and other clients, then sends back a response. When you type a URL into your browser, something on the other end has to receive that request and reply with a web page, an image, or some data. That “something” is a web server. Understanding what it does — and what it does not do — is the foundation of almost everything else in DevOps, because nearly every deployment puts a web server in front of your application.
What a web server actually does
At its core, a web server speaks HTTP (HyperText Transfer Protocol — the language browsers and servers use to talk to each other) and its secure version, HTTPS (HTTP plus encryption). A client (usually a browser) sends an HTTP request like “GET me the page at /about”, and the server sends back an HTTP response: a status code (like 200 OK or 404 Not Found), some headers, and a body (the actual content).
The web server’s job breaks down into two main patterns:
- Serving static files — handing back files that already exist on disk exactly as they are.
- Proxying dynamic requests — passing the request to another program (your application) and returning whatever that program produces.
Almost every real website uses both at the same time.
Static content vs dynamic content
Static content is any file that does not change based on who is asking or when. Think HTML files, CSS stylesheets, JavaScript bundles, images, fonts, and PDFs. The server just reads the file from disk and sends it back. This is extremely fast because no thinking is involved — the file is already finished.
Dynamic content is generated on the fly. When you log into a bank and see your balance, that page did not exist as a file. Your application (written in Node.js, Python, PHP, Java, etc.) built it for you in that moment by talking to a database. A plain web server cannot do this by itself — it has to forward the request to your application.
This is where the term reverse proxy comes in: a reverse proxy is a server that sits in front of your application and forwards incoming requests to it, then passes the application’s response back to the client. The web server handles the public-facing HTTP details (TLS encryption, compression, logging) while your app focuses only on business logic.
| Aspect | Static content | Dynamic content |
|---|---|---|
| Source | A file already on disk | Generated by your app per request |
| Examples | style.css, logo.png, index.html | A user’s dashboard, search results |
| Who handles it | Web server alone | Web server forwards to app server |
| Speed | Very fast | Depends on your app + database |
| When to use | Assets, marketing pages, JS/CSS | Anything personalized or data-driven |
Where a web server sits in the stack
Picture the path a request takes from a user’s browser to your code:
Browser → Internet → Web server (Nginx/Apache) → App server (your code) → Database
│
└── serves static files directly (no app needed)
The web server is the front door. Everything from the outside world hits it first. For requests like /css/app.css it answers directly from disk. For requests like /api/orders it forwards to your application running on a local port (for example http://127.0.0.1:3000).
An app server (sometimes called an application server or upstream) is the program that runs your code — your Express app, your Django project, your Spring Boot service. It usually listens only on localhost, hidden from the internet, and the web server is the only thing allowed to talk to it.
Why put a web server in front of your app at all? It is tempting to let your Node or Python app face the internet directly. Don’t. A dedicated web server gives you TLS/HTTPS termination, request buffering against slow clients, gzip compression, caching, rate limiting, and clean access logs — all battle-tested and far more secure than rolling your own.
The two dominant choices: Nginx and Apache
For decades, two web servers have dominated Linux servers: Nginx (pronounced “engine-x”) and Apache HTTP Server (often just “Apache” or “httpd”).
- Nginx was built to handle many connections at once with very little memory. It is the default choice today for serving static files fast and acting as a reverse proxy or load balancer. Its config lives in
/etc/nginx. - Apache is older, hugely flexible, and famous for its module system and per-directory
.htaccessfiles. It is still extremely common, especially with PHP and shared hosting. Its config lives in/etc/apache2on Ubuntu.
| Nginx | Apache | |
|---|---|---|
| Best at | Static files, reverse proxy, load balancing | Flexible config, .htaccess, legacy PHP |
| Concurrency model | Event-driven (handles thousands of connections cheaply) | Process/thread per connection (heavier) |
| Per-directory config | No (central config only) | Yes (.htaccess) |
| Ubuntu config path | /etc/nginx/sites-available | /etc/apache2/sites-available |
| When to choose | New projects, high traffic, microservices | Existing Apache setups, shared hosting, .htaccess needs |
On a fresh Ubuntu 22.04 or 24.04 LTS server, you install either one with apt and manage it with systemd:
sudo apt update
sudo apt install nginx -y
sudo systemctl status nginx
Output:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-06-15 10:02:11 UTC; 3s ago
Main PID: 1423 (nginx)
Tasks: 2 (limit: 1131)
Memory: 2.6M
Once it is running, open the firewall so visitors can reach it:
sudo ufw allow 'Nginx Full'
sudo ufw status
Output:
Status: active
To Action From
-- ------ ----
Nginx Full ALLOW Anywhere
Nginx Full (v6) ALLOW Anywhere (v6)
Nginx Full opens both port 80 (HTTP) and port 443 (HTTPS). If you only need plain HTTP for testing, use Nginx HTTP instead.
Best Practices
- Always run a real web server in front of your app — never expose Node, Python, or Java app servers directly to the internet.
- Serve static assets from the web server, not your app. It is faster and frees your application to do real work.
- Terminate HTTPS at the web server so your app only ever sees plain
localhosttraffic. - Pick Nginx for new projects unless you specifically need Apache features like
.htaccess. - Open only the ports you need with
ufw(80 and 443), and keep your app’s port closed to the outside world. - Check logs in
/var/log/nginxor/var/log/apache2when something breaks — the answer is almost always there. - Keep the package updated with
sudo apt update && sudo apt upgradeto receive security patches.