Apache vs Nginx: Which Web Server Should a DevOps Engineer Learn?
Most people learning DevOps jump straight into Docker, Kubernetes, Terraform, and CI/CD.
But there is a layer underneath all of them that is easy to overlook:
The web server.
When a browser requests:
something has to receive that request, decide what should happen to it, serve static files, terminate SSL, or forward the request to an application.
That is where web servers such as Apache and Nginx become essential.
Understanding both isn't just useful for interviews.
It helps you understand how real Linux-based applications are deployed.
What Is a Web Server?
A web server accepts HTTP/HTTPS requests and returns responses to clients.
A simplified architecture looks like this:
Client
|
| HTTP/HTTPS
v
Nginx / Apache
|
+---- Static files
|
+---- PHP-FPM
|
+---- Node.js
|
+---- Python
|
+---- Backend API
A web server can therefore do much more than simply serve an index.html file.
It can handle:
HTTP/HTTPS
SSL/TLS termination
Virtual hosting
Reverse proxying
Load balancing
Static file delivery
Access and error logging
Compression
Security headers
Request routing
Apache Web Server Administration
Apache is one of the most established web servers in the Linux ecosystem.
On Ubuntu, installation is straightforward:
sudo apt update
sudo apt install apache2
Start Apache:
sudo systemctl start apache2
Enable it at boot:
sudo systemctl enable apache2
Check its status:
sudo systemctl status apache2
But there is one command every Linux administrator should get into the habit of using:
sudo apache2ctl configtest
It checks the configuration before you reload or restart Apache.
Never blindly restart a production web server after changing its configuration.
Test first.
Apache Virtual Hosts
One of Apache's most important concepts is the Virtual Host.
Virtual hosts allow one server to host multiple websites.
For example:
example.com
blog.example.com
shop.example.com
Each can have its own configuration.
A basic virtual host might look like:
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
Then enable the site:
sudo a2ensite example.com.conf
Test the configuration:
sudo apache2ctl configtest
Reload Apache:
sudo systemctl reload apache2
The important pattern is:
Edit
↓
Test
↓
Reload
Not:
Edit
↓
Restart
↓
Hope
Apache as a Reverse Proxy
Apache can also sit in front of an application.
Suppose your Node.js application runs on:
127.0.0.1:3000
Apache can expose it publicly:
Internet
|
v
Apache :80
|
v
Node.js :3000
The configuration uses:
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
This is a fundamental DevOps pattern.
The application doesn't necessarily need to listen directly on the public interface.
The web server becomes the controlled entry point.
Nginx Web Server Administration
Nginx is another essential tool for Linux and DevOps engineers.
Install it with:
sudo apt update
sudo apt install nginx
Start it:
sudo systemctl start nginx
Enable it at boot:
sudo systemctl enable nginx
Check the service:
sudo systemctl status nginx
Before changing anything in production:
sudo nginx -t
If the configuration is valid, reload:
sudo systemctl reload nginx
Nginx Server Blocks
Nginx uses server blocks for virtual hosting.
A typical configuration looks like:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
A common Linux workflow is:
sudo nano /etc/nginx/sites-available/example.com
Then create the symbolic link:
sudo ln -s /etc/nginx/sites-available/example.com \
/etc/nginx/sites-enabled/
Test:
sudo nginx -t
Reload:
sudo systemctl reload nginx
Nginx as a Reverse Proxy
This is where Nginx becomes especially useful in modern application deployments.
Imagine your application runs on:
127.0.0.1:3000
Nginx can receive public traffic on port 80:
User
|
v
Nginx :80
|
v
Application :3000
Example:
server {
listen 80;
server_name nodeapp.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}
Now the user doesn't need to know that the application is running on port 3000.
Nginx handles the public-facing request.
Nginx Load Balancing
Nginx can also distribute traffic across multiple backend servers.
For example:
+--> App Server 1
|
Client --> Nginx +--> App Server 2
|
+--> App Server 3
A basic upstream configuration:
upstream backend {
least_conn;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
Then proxy requests to it:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Instead of sending every request to one application instance, Nginx can distribute requests across multiple backends.
This becomes important when building applications that need greater availability and scalability.
HTTPS and SSL
Modern applications should use HTTPS.
Both Apache and Nginx can terminate SSL/TLS.
For Nginx, an HTTPS server block can include:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
}
A common architecture is:
Client
|
HTTPS
|
v
Nginx
|
HTTP/internal network
|
v
Application
This allows Nginx to handle TLS while the backend application focuses on application logic.
Apache vs Nginx
So which one should you learn?
The answer isn't simply:
"Nginx is better."
The tools solve overlapping problems but have different strengths.
Learn both.
You don't want to be the engineer who knows only one command because every production environment is different.
Apache and Nginx Can Work Together
You don't always have to choose one.
A common architecture is:
Internet
|
v
Nginx
|
+---- Static files
|
+---- Apache
|
+---- PHP/Application
Nginx can act as the public-facing reverse proxy while Apache handles application-specific functionality.
The important thing isn't choosing a "winner."
It's understanding where each component belongs in the request path.
Restart vs Reload: A Small Detail That Matters
This is one of those concepts that looks trivial until you're troubleshooting production.
Restart
sudo systemctl restart nginx
The service is stopped and started again.
Reload
sudo systemctl reload nginx
Nginx re-reads its configuration without completely stopping the service.
That's why configuration changes often follow this pattern:
sudo nginx -t
sudo systemctl reload nginx
The principle is simple:
Validate first. Change second.
Important Linux Web Server Files
If you're preparing for a DevOps interview, know where the configuration lives.
Apache
/etc/apache2/apache2.conf
/etc/apache2/ports.conf
/etc/apache2/sites-available/
/etc/apache2/sites-enabled/
/etc/apache2/mods-available/
/etc/apache2/mods-enabled/
/var/log/apache2/
/var/www/html/
Nginx
/etc/nginx/nginx.conf
/etc/nginx/sites-available/
/etc/nginx/sites-enabled/
/etc/nginx/conf.d/
/var/log/nginx/
/var/www/html/
Knowing these paths makes troubleshooting much faster.
The Most Important Troubleshooting Habit
When a web server stops working, don't randomly restart services.
Work from the bottom up.
- Check the service sudo systemctl status nginx
- Validate configuration sudo nginx -t
- Check listening ports sudo ss -tulpn
- Check logs sudo tail -f /var/log/nginx/error.log
- Test locally curl http://127.0.0.1
- Check connectivity
Then investigate:
firewall rules
DNS
security groups
backend availability
application ports
file permissions
This approach is much more reliable than guessing.
What DevOps Engineers Should Actually Learn
Don't memorize hundreds of configuration directives.
Understand the request flow:
DNS
↓
Load Balancer
↓
Nginx / Apache
↓
Reverse Proxy
↓
Application
↓
Database
Then learn how to troubleshoot each layer.
Because production incidents rarely announce themselves with:
"The problem is on line 47 of nginx.conf."
Instead, you get:
502 Bad Gateway
Connection refused
403 Forbidden
404 Not Found
500 Internal Server Error
Your job is to find which layer failed and why.
Final Takeaway
Apache and Nginx aren't just "web server packages."
They are fundamental building blocks of Linux infrastructure.
If you're serious about DevOps, learn how to:
Install and manage Apache and Nginx
Configure virtual hosts/server blocks
Serve static websites
Configure HTTPS
Use reverse proxies
Configure load balancing
Read access and error logs
Test configurations safely
Understand reload vs restart
Troubleshoot ports and connectivity
The deeper lesson is even more important:
DevOps isn't about memorizing commands.
It's about understanding how systems communicate.
Once you understand the request path, the commands start making sense.
And that's when Linux administration stops feeling like a collection of random commands—and starts becoming engineering.
Frequently Asked Questions
Is Apache better than Nginx?
Not universally. Apache is particularly useful when .htaccess and per-directory configuration are important. Nginx is commonly used for high-concurrency workloads, static content, reverse proxying, and load balancing.
Is Nginx a web server or a reverse proxy?
Both. Nginx can directly serve static content and can also act as a reverse proxy in front of applications.
What command checks Nginx configuration?
sudo nginx -t
What command checks Apache configuration?
sudo apache2ctl configtest
What is a reverse proxy?
A reverse proxy receives client requests and forwards them to backend servers or applications.
Why use Nginx in front of an application?
It can provide a controlled public entry point while handling tasks such as TLS termination, request routing, static content, and load balancing.
Should DevOps engineers learn Apache and Nginx?
Yes. Understanding both makes it easier to work with existing Linux infrastructure and troubleshoot real production environments.
If you remember only one thing:
Don't memorize the web server. Understand the request.
That mindset will take you much further in DevOps than memorizing configuration files.
Part 2: Apache & Nginx Web Server Administration
2.1 Apache Web Server
Installation & Basic Management
# Install Apache
sudo apt update
sudo apt install apache2
# Service management
sudo systemctl start apache2
sudo systemctl stop apache2
sudo systemctl restart apache2
sudo systemctl reload apache2 # Reload config without downtime
sudo systemctl enable apache2 # Start on boot
sudo systemctl status apache2
# Test configuration before restart
sudo apache2ctl configtest # ALWAYS do this before restart
sudo apachectl -t # Alternative
# Check Apache version
apache2 -v
# List enabled modules
apache2ctl -M
Apache Virtual Hosts (CRITICAL - You WILL be asked)
# Create virtual host config
sudo nano /etc/apache2/sites-available/example.com.conf
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
ServerAdmin admin@example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
</VirtualHost>
# HTTPS Virtual Host (with SSL)
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_ssl_access.log combined
</VirtualHost>
# HTTP to HTTPS redirect
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
# Enable the site
sudo a2ensite example.com.conf
# Disable a site
sudo a2dissite example.com.conf
# Enable required modules
sudo a2enmod rewrite # For .htaccess URL rewriting
sudo a2enmod ssl # For HTTPS
sudo a2enmod headers # For security headers
sudo a2enmod proxy # For reverse proxy
sudo a2enmod proxy_http # For HTTP reverse proxy
sudo a2enmod expires # For caching
# Disable a module
sudo a2dismod module_name
# Create document root and set permissions
sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com
# Test and reload
sudo apache2ctl configtest
sudo systemctl reload apache2
Apache as Reverse Proxy (for Node.js, Python apps)
# /etc/apache2/sites-available/nodeapp.com.conf
<VirtualHost *:80>
ServerName nodeapp.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
ErrorLog ${APACHE_LOG_DIR}/nodeapp_error.log
CustomLog ${APACHE_LOG_DIR}/nodeapp_access.log combined
</VirtualHost>
Apache .htaccess (Common Rules)
# /var/www/example.com/public/.htaccess
# Enable URL rewriting
RewriteEngine On
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# Laravel/PHP Framework front controller
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# Deny access to sensitive files
<FilesMatch "\.(env|htpasswd|ini|log|sh|sql)$">
Require all denied
</FilesMatch>
# Security headers
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json
</IfModule>
# Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
Apache Key Files & Directories
| Path | Purpose |
|---|---|
/etc/apache2/apache2.conf |
Main config file |
/etc/apache2/ports.conf |
Listening ports |
/etc/apache2/sites-available/ |
Available virtual host configs |
/etc/apache2/sites-enabled/ |
Enabled virtual hosts (symlinks) |
/etc/apache2/mods-available/ |
Available modules |
/etc/apache2/mods-enabled/ |
Enabled modules (symlinks) |
/var/log/apache2/error.log |
Error log |
/var/log/apache2/access.log |
Access log |
/var/www/html/ |
Default document root |
2.2 Nginx Web Server
Installation & Basic Management
# Install Nginx
sudo apt update
sudo apt install nginx
# Service management
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
sudo systemctl enable nginx
sudo systemctl status nginx
# Test configuration (ALWAYS before restart/reload)
sudo nginx -t
# Check Nginx version
nginx -v # Version only
nginx -V # Version + compile options
Nginx Server Blocks (Virtual Hosts)
sudo nano /etc/nginx/sites-available/example.com
# /etc/nginx/sites-available/example.com
# HTTP - redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Document root
root /var/www/example.com/public;
index index.php index.html;
# SSL
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Logging
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
# Main location
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP processing (PHP-FPM)
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Block sensitive files
location ~* \.(env|log|sql|sh|bak)$ {
deny all;
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
gzip_min_length 256;
}
# Enable site (create symlink)
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Disable site
sudo rm /etc/nginx/sites-enabled/example.com
# Test and reload
sudo nginx -t
sudo systemctl reload nginx
Nginx as Reverse Proxy (for Node.js, Python, etc.)
# /etc/nginx/sites-available/nodeapp.com
server {
listen 80;
server_name nodeapp.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 300;
}
}
Nginx Load Balancing
# Load balancing across multiple backend servers
upstream backend {
least_conn; # Load balancing method
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Nginx Key Files & Directories
| Path | Purpose |
|---|---|
/etc/nginx/nginx.conf |
Main config file |
/etc/nginx/sites-available/ |
Available server block configs |
/etc/nginx/sites-enabled/ |
Enabled server blocks (symlinks) |
/etc/nginx/conf.d/ |
Additional config files |
/var/log/nginx/error.log |
Error log |
/var/log/nginx/access.log |
Access log |
/var/www/html/ |
Default document root |
Nginx Performance Tuning (/etc/nginx/nginx.conf)
worker_processes auto; # Match CPU cores
worker_connections 1024; # Connections per worker
# In http block:
keepalive_timeout 65;
client_max_body_size 100M; # Max upload size
server_tokens off; # Hide Nginx version
# Buffer sizes
client_body_buffer_size 10K;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
2.3 Apache vs Nginx Comparison
| Feature | Apache | Nginx |
|---|---|---|
| Architecture | Process/Thread-based | Event-driven, async |
| Performance | Good for dynamic content | Better for static content & high concurrency |
| Config style |
.htaccess per-directory |
Centralized config only |
| PHP handling | mod_php (built-in) | PHP-FPM (external) |
| Reverse proxy | mod_proxy | Built-in, excellent |
| Memory usage | Higher | Lower |
| Use case | Shared hosting, .htaccess needed | High-traffic, reverse proxy, microservices |
| Config test | apache2ctl configtest |
nginx -t |
| Reload | systemctl reload apache2 |
systemctl reload nginx |
Interview Q&A: Web Servers
| Question | Answer |
|---|---|
| When would you use Apache over Nginx? | When you need .htaccess support, mod_php, or per-directory config (shared hosting) |
| When would you use Nginx over Apache? | High-traffic sites, reverse proxy, static files, lower memory footprint |
| Can you use both together? | Yes! Nginx as reverse proxy in front of Apache. Nginx handles static files, Apache handles PHP |
What is try_files? |
Nginx directive that checks for files in order, falls back to last option (usually index.php for frameworks) |
What's AllowOverride All in Apache? |
Enables .htaccess files in that directory to override server config |
| How to test config before restarting? | Apache: apache2ctl configtest. Nginx: nginx -t
|
| What's the difference between restart and reload? | Restart: stops and starts (brief downtime). Reload: re-reads config without stopping (zero downtime) |
What is proxy_pass? |
Nginx directive to forward requests to another server (reverse proxy) |
| How do you handle file upload size limits? | Apache: LimitRequestBody or PHP upload_max_filesize. Nginx: client_max_body_size
|
What is worker_processes auto? |
Sets Nginx worker processes to match CPU cores automatically |

Top comments (2)
Sure. This paragraph is saying that a web server is much more than a program that sends an HTML page to your browser.
Think of a web server like a security guard + traffic controller + delivery person for a website.
Let's understand each term simply.
1. HTTP / HTTPS
These are the protocols used for communication between your browser and the web server.
When you type:
https://example.comyour browser communicates with the web server using HTTPS.
2. SSL/TLS termination
SSL/TLS is what provides the encryption in HTTPS.
Suppose a user sends:
The web server receives the encrypted connection and decrypts it.
This process is commonly called TLS termination.
So:
The web server handles the encryption instead of making your application handle it directly.
3. Virtual hosting
One physical server can host many websites.
For example:
When a request comes in:
the web server knows:
This is called virtual hosting.
4. Reverse proxying
A web server can sit in front of your application server.
For example:
The user doesn't directly communicate with Node.js.
Apache receives the request and forwards it to Node.js.
That's reverse proxying.
5. Load balancing
Suppose your website becomes very popular.
Instead of having one application server:
you can have:
Apache can distribute incoming requests between the servers.
For example:
This is load balancing.
6. Static file delivery
A web server can directly send files such as:
These are called static files because they don't need your application to generate them dynamically.
For example:
Apache can simply find the file and send it back.
7. Access and error logging
A web server can record what happens.
For example, an access log might contain:
An error log might record:
These logs help administrators troubleshoot problems.
8. Compression
Web servers can compress responses before sending them.
Without compression:
With compression:
This can make websites load faster and reduce bandwidth usage.
Common compression methods include gzip and Brotli.
9. Security headers
A web server can add HTTP headers that improve security.
For example:
These headers tell the browser how it should handle certain types of content and connections.
10. Request routing
The server can decide where a request should go.
For example:
So Apache can act like a traffic controller:
So what does "Apache Web Server Administration" mean?
It means learning how to install, configure, secure, monitor, and maintain Apache.
For example, an Apache administrator might configure:
The big picture
Instead of thinking:
think:
Apache can therefore sit at the front of your infrastructure:
That's why Apache Web Server Administration is much broader than simply learning how to put an
index.htmlfile in a directory.Here culprit is the upstream app or proxy config, not the gateway itself.
502 Bad Gateway: the gateway/proxy (Nginx/Apache) can’t get a valid response from the upstream app. Check that the app is running and listening on the configured port/socket, and that proxy_pass/ProxyPass targets are correct.
403 Forbidden: access control or filesystem permissions blocked the request at the server or app level; review file permissions, .htaccess/nginx deny rules, or firewall rules.
404 Not Found: requested resource doesn’t exist on the upstream or routing misconfiguration; confirm routes, upstream server paths, and alias/roots.
500 Internal Server Error: error inside the application; inspect app logs, stack traces, and misconfigurations