Web servers on a VPS: nginx, Apache and Caddy
A web server is the program that answers when someone visits your site. On a VPS you choose it yourself, and the three common choices are nginx, Apache and Caddy. All three are free, mature and fast enough for almost any small or medium site. They differ in how they are configured, how they handle HTTPS, and how well they fit the software you plan to run.
This guide explains what a web server does, compares the three, shows a minimal working config for each, and explains where app servers such as Gunicorn, Node.js and PHP-FPM fit. By the end you should know which one to install for your site, and why.
1 GB and 2 GB VPS plans in stock, enough for most small sites
- 1 GB KVM VPS Special1 CPU · 1 GB RAM · 20 GB disk · 3 TB bandwidth · New York, Los Angeles, Dallas$21.99/yrabout $1.83/moView at RackNerd
- Storage VPS Special - 100GB Storage1 CPU · 1 GB RAM · 100 GB disk · 10 TB bandwidth · Los Angeles, Dallas, New York$35.00/yrabout $2.92/moView at RackNerd
- 2 GB KVM VPS Special2 CPU · 2 GB RAM · 35 GB disk · 5 TB bandwidth · New York, Los Angeles, Dallas$35.99/yrabout $3.00/moView at RackNerd
- Ryzen-1GB (Linux)1 CPU · 1 GB RAM · 15 GB disk · 1 TB bandwidth · New York, Chicago, Seattle$194.29/yrabout $16.19/moView at RackNerd
In stock when this page loaded. See all 7 matching plans
What is in this guide
- A few words you will see
- What a web server does
- The three side by side
- Which one to pick
- Caddy: a minimal setup
- nginx: a minimal setup
- Apache: a minimal setup
- App servers: Gunicorn, Node.js and PHP-FPM
- One web server, several apps
- Troubleshooting
- Limits
- When to step up
A few words you will see
| Term | What it means |
|---|---|
| Web server | The program that receives visits on ports 80 and 443 and sends back pages. |
| Static site | Plain files such as HTML, CSS and images, sent as they are. |
| Dynamic site | Pages built by code, such as PHP or Python, often using a database. |
| App server | A program that runs your site's code and hands the result to the web server. |
| Reverse proxy | A web server that passes visits on to an app running on another port. |
| Virtual host | The settings for one site on a web server. One server can host many. |
| HTTPS certificate | What lets browsers connect securely. Free ones are available and renew automatically. |
| .htaccess | Per-folder settings files that only Apache reads. |
What a web server does
When a visitor opens your site, their browser connects to your server on port 443, the HTTPS port. The web server:
- Handles the secure connection using your certificate.
- Looks at which domain was asked for, so one server can host many sites.
- Either sends a file from disk, for static content, or passes the request to an app server, for dynamic content.
- Sends the answer back, and writes a line in its access log.
Only one program can listen on ports 80 and 443 at a time. If you run several sites or apps, one web server handles all of them and passes each request to the right place.
The three side by side
General traits. Each can do almost anything the others can, with enough configuration.
| Caddy | nginx | Apache | |
|---|---|---|---|
| HTTPS certificates | Automatic, built in | Added with a tool such as Certbot | Added with a tool such as Certbot |
| Config style | Short and readable | Compact blocks, more detail | Longer, very flexible |
| .htaccess files | No | No | Yes |
| Reverse proxy | Very easy | Easy and very common | Works well with extra modules |
| PHP sites | Through PHP-FPM | Through PHP-FPM | Through PHP-FPM, and the best fit for software that ships .htaccess rules |
| Memory use | Low | Very low | Low to moderate, depending on setup |
| Help online | Good and growing | Huge | Huge, going back many years |
| Best known for | Simplicity and automatic HTTPS | Speed and reverse proxying | Compatibility with PHP apps and shared-hosting habits |
Which one to pick
| Your situation | A good choice | Why |
|---|---|---|
| A static site or a few Docker apps, and you want it simple | Caddy | HTTPS works with almost no setup |
| Python, Node.js or other apps behind a proxy | Caddy or nginx | Both are excellent reverse proxies |
| WordPress or other PHP apps with plugins that write .htaccess rules | Apache | Those rules just work |
| Moving a site from cPanel shared hosting | Apache | cPanel uses Apache, so your .htaccess files keep working |
| Following a tutorial written for one of them | The one it uses | Translating configs is a common source of mistakes |
| A very busy site on a small plan | nginx | Very efficient, and widely tuned for heavy traffic |
If you have no strong reason either way, start with Caddy for apps and static sites, and Apache for PHP apps. You can change later.
Caddy: a minimal setup
Caddy is in the package lists of recent Ubuntu and Debian releases. If it is not found, follow the install steps in Caddy's own documentation.
sudo apt install caddy
sudo nano /etc/caddy/Caddyfile
Replace the file's contents with this, using your own domain:
example.com, www.example.com {
root * /var/www/example
file_server
encode gzip
}
Put your files in /var/www/example, open the web ports with sudo ufw allow 80,443/tcp, and reload:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Caddy gets and renews the HTTPS certificate by itself, as long as your domain points at the server. See domains and DNS explained. For an app instead of files, a site block can be as short as this:
app.example.com {
reverse_proxy 127.0.0.1:8080
}
nginx: a minimal setup
sudo apt install nginx
sudo nano /etc/nginx/sites-available/example.com
Put this in it:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Enable the site, check it and reload:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Then add HTTPS with Certbot, which edits the config for you and sets up renewals:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
To proxy an app instead of serving files, replace the location block with:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
The proxy_set_header lines tell the app the visitor's real address and that the original visit used HTTPS.
Apache: a minimal setup
sudo apt install apache2
sudo nano /etc/apache2/sites-available/example.com.conf
Put this in it:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example
<Directory /var/www/example>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
AllowOverride All lets .htaccess files in the site work. Enable it, check it and reload, then add HTTPS:
sudo a2ensite example.com
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com
For a full PHP setup with Apache, see installing WordPress on a VPS, which runs PHP as the site's own user.
2 GB and 4 GB VPS plans in stock, for dynamic sites and apps
- 2 GB KVM VPS Special2 CPU · 2 GB RAM · 35 GB disk · 5 TB bandwidth · New York, Los Angeles, Dallas$35.99/yrabout $3.00/moView at RackNerd
- 4 GB KVM VPS Special3 CPU · 4 GB RAM · 60 GB disk · 7 TB bandwidth · New York, Los Angeles, Dallas$59.99/yrabout $5.00/moView at RackNerd
- Ryzen-2GB (Linux)2 CPU · 2 GB RAM · 20 GB disk · 2 TB bandwidth · New York, Chicago, Seattle$222.37/yrabout $18.53/moView at RackNerd
- KVM-2GB3 CPU · 2 GB RAM · 75 GB disk · 2 TB bandwidth · Los Angeles, Dallas, New York$247.08/yrabout $20.59/moView at RackNerd
In stock when this page loaded. See all 6 matching plans
App servers: Gunicorn, Node.js and PHP-FPM
Web servers are great at files and connections but do not run your code. For dynamic sites, an app server runs the code, and the web server passes requests to it. Keep the app server listening on 127.0.0.1 or a local socket, so only the web server can reach it.
| Language | Common app server | How the web server reaches it |
|---|---|---|
| PHP | PHP-FPM | A local socket, through FastCGI |
| Python (Django, Flask) | Gunicorn or uvicorn | Reverse proxy to a local port |
| Node.js | The app itself | Reverse proxy to a local port |
| Ruby, Go and others | The app or its own server | Reverse proxy to a local port |
| Docker apps | The container | Reverse proxy to a port published on 127.0.0.1 |
A Python app with Gunicorn, as an example. Install Gunicorn in your app's virtual environment as its documentation explains, then run it as a service so it starts at boot. Create /etc/systemd/system/myapp.service:
[Unit]
Description=My Python app
After=network.target
[Service]
User=myapp
WorkingDirectory=/home/myapp/app
ExecStart=/home/myapp/app/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 myapp.wsgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.target
Change the user, folders and the last part of the ExecStart line to match your app. Then start it with sudo systemctl daemon-reload and sudo systemctl enable --now myapp, and point your web server's reverse proxy at 127.0.0.1:8000. Each Gunicorn worker uses memory, so start with two or three on a small plan.
One web server, several apps
A single web server can host many sites and apps, each on its own domain or subdomain:
- example.com served from files.
- blog.example.com passed to a PHP pool.
- notes.example.com passed to a Docker app on
127.0.0.1:8081. - status.example.com passed to another app on
127.0.0.1:8082.
Each one is a separate site block or virtual host. The web server handles HTTPS for all of them, and the apps stay private behind it. Before making any app public, make sure it has a login and that open sign-up is off. For apps only you use, keep them off the web entirely and reach them over a VPN. See setting up a WireGuard VPN.
Installing and using Docker on a VPS covers publishing Docker apps safely behind a web server.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Web server will not start, "address already in use" | Another web server already uses port 80 or 443 | Run sudo ss -tlnp to see which. Stop or remove one of them |
| Site does not load at all | Firewall closed, or DNS not pointing at the server | Run sudo ufw status and dig example.com +short |
| Certificate fails to issue | DNS not ready, or port 80 blocked | Fix DNS and open port 80, then try again |
| 502 Bad Gateway | The app behind the proxy is not running, or uses another port | Check the app's service and logs, and the port in the config |
| 403 Forbidden | The web server cannot read the files | Check folder permissions and the folder named in the config |
| 404 for pages that exist | Wrong folder in the config, or missing rewrite rules | Check the root folder. On nginx, rewrite rules from .htaccess must be translated |
| Redirect loop | The app does not know the visit came over HTTPS | Pass the forwarded headers shown in the nginx section, or set the app's own HTTPS setting |
| Changes have no effect | Config not reloaded, or a syntax error | Run the check command for your server, then reload |
Limits
- Speed comes from your site more than the server. All three are fast. Caching, image sizes and database queries matter far more than which web server you pick.
- Configs are not interchangeable. Rules written for one do not work in another. Translate carefully, and test.
- Package versions vary. Your distribution may ship an older release than the project's own packages. Both work for most sites.
- It is your job to keep it updated. Web servers face the internet directly, so turn on automatic security updates. See securing your VPS.
- Fair use applies. Unmetered does not mean unlimited. A popular site with large files uses bandwidth, and RackNerd's acceptable use policy covers what you serve.
When to step up
The web server is rarely what runs out of memory. The apps behind it, especially databases and PHP or Python workers, use far more. Step up when those apps leave little memory free, or when you host several dynamic sites on one server. A 4 GB plan suits several apps comfortably, and 6 GB or 8 GB gives room for busy sites with databases.
4 GB to 8 GB VPS plans in stock, for several sites and apps
- 4 GB KVM VPS Special3 CPU · 4 GB RAM · 60 GB disk · 7 TB bandwidth · New York, Los Angeles, Dallas$59.99/yrabout $5.00/moView at RackNerd
- 6 GB KVM VPS Special6 CPU · 6 GB RAM · 100 GB disk · 12 TB bandwidth · New York, Los Angeles, Dallas$89.99/yrabout $7.50/moView at RackNerd
- 8 GB KVM VPS Special7 CPU · 8 GB RAM · 150 GB disk · 20 TB bandwidth · New York, Los Angeles, Dallas$119.99/yrabout $10.00/moView at RackNerd
- Ryzen-4GB (Linux)2 CPU · 4 GB RAM · 30 GB disk · 3 TB bandwidth · New York, Chicago, Seattle$265.57/yrabout $22.13/moView at RackNerd
In stock when this page loaded. See all 9 matching plans
To compare what is available now, start with the cheapest RackNerd VPS guide and check the sales page.