Nginx Web Server¶
Nginx (pronounced "engine-x") is a fast, event-driven web server and reverse proxy. It is the most common choice for serving static content and for sitting in front of application servers. This page covers installing nginx, understanding its configuration layout, writing a server {} block, and serving a simple static site.
Tested on
AlmaLinux 9 / RHEL 9 with nginx from the AppStream repository. Debian/Ubuntu notes are included inline.
Nginx vs Apache (quick framing)¶
| nginx | Apache httpd | |
|---|---|---|
| Architecture | Event-driven, async (handles many idle connections cheaply) | Process/thread per connection (MPM models) |
| Static files | Very fast | Fast |
| Per-directory config | No .htaccess — central config only |
.htaccess supported |
| Dynamic content | Proxies to PHP-FPM / app servers | Can embed modules (mod_php) or proxy |
There is no universally "better" one. Nginx tends to win for high-concurrency static/reverse-proxy workloads; Apache is convenient when you rely on .htaccess or per-directory overrides. See Apache for the Apache equivalent of everything here.
Install nginx¶
Open the firewall¶
On RHEL/AlmaLinux, firewalld blocks ports 80/443 by default. Add the predefined http and https services:
# --permanent writes the rule; without it the change is lost on reload
sudo firewall-cmd --add-service=http --add-service=https --permanent
# Apply the permanent rules to the running firewall
sudo firewall-cmd --reload
# Verify
sudo firewall-cmd --list-services
On Debian/Ubuntu with UFW you would instead run sudo ufw allow 'Nginx Full'. See Firewalls for a deeper look at firewalld and UFW.
You should now be able to browse to http://<server-ip>/ and see the default nginx welcome page.
Configuration structure¶
The main configuration file is /etc/nginx/nginx.conf. Rather than editing it directly, you add site-specific files that it includes.
/etc/nginx/
├── nginx.conf # main config; contains: include /etc/nginx/conf.d/*.conf;
├── conf.d/ # drop your *.conf site files here
│ └── default.conf # (if present) the default site
├── mime.types
└── /usr/share/nginx/html/ # default document root
By default nginx.conf ends its http {} block with:
So any *.conf file you place in conf.d/ is loaded automatically.
Debian uses the sites-available / sites-enabled pattern:
/etc/nginx/
├── nginx.conf
├── sites-available/ # write your site files here
│ └── default
└── sites-enabled/ # symlinks to enabled sites (this is what nginx reads)
You enable a site by symlinking it:
Unlike Apache there is no nsite helper command — you create the symlink yourself.
Anatomy of a server {} block¶
A server {} block defines a virtual host — how nginx should respond for a given hostname/port. The key directives:
server {
listen 80; # port (and optionally address) to listen on
listen [::]:80; # also listen on IPv6
server_name example.com www.example.com; # which Host headers match this block
root /var/www/example.com; # filesystem path for this site's files
index index.html index.htm; # default file to serve for a directory
# location matches request URIs
location / {
# try the exact file, then a directory, else 404
try_files $uri $uri/ =404;
}
# separate log files make per-site troubleshooting easy
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}
listen— the port (and optional IP/protocol).listen 80;is HTTP;listen 443 ssl;is HTTPS.server_name— the hostname(s) this block answers for. Nginx uses the request'sHostheader to pick the matching block.root— the directory on disk where the site's files live.index— which file to serve when a directory is requested.location— matches request paths and decides how to handle them.try_filesis the workhorse for static sites.
Hosting several sites at once is covered in Virtual Hosts.
Serve a simple static site¶
1. Create the document root and a page¶
# Create a directory for the site
sudo mkdir -p /var/www/example.com
# A minimal landing page
echo '<h1>Hello from nginx on AlmaLinux 9</h1>' | sudo tee /var/www/example.com/index.html
2. SELinux: label a non-default document root¶
This is the step most often missed on RHEL/AlmaLinux. Files under a custom root like /var/www/... do not have the SELinux type nginx is allowed to read (httpd_sys_content_t), so you will get 403 Forbidden even though file permissions look fine.
# Add a persistent SELinux file-context rule for the new root (and everything under it)
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
# Apply the new context to existing files
sudo restorecon -Rv /var/www/example.com
semanage not found?
semanage ships in the policycoreutils-python-utils package: sudo dnf install -y policycoreutils-python-utils.
Apps that connect outward
If your site (or a reverse-proxied backend) needs nginx to open network connections — e.g. proxying to an upstream or contacting a remote database — also run sudo setsebool -P httpd_can_network_connect 1. The -P makes it persist across reboots. More on SELinux in SELinux.
3. Add the site config¶
Create /etc/nginx/conf.d/example.com.conf (on Debian, create it under sites-available and symlink it):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}
4. Test the config, then reload¶
Always test before reloading — a syntax error in any included file will stop nginx from restarting.
Expected output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Then reload (graceful — no dropped connections, unlike restart):
Logs¶
Nginx logs live in /var/log/nginx/:
| File | Contents |
|---|---|
/var/log/nginx/access.log |
Every request (default site) |
/var/log/nginx/error.log |
Errors, warnings, startup messages |
/var/log/nginx/example.com.*.log |
Your per-site logs from the config above |
# Watch errors live while you test
sudo tail -f /var/log/nginx/error.log
# Watch a specific site's requests
sudo tail -f /var/log/nginx/example.com.access.log
403 Forbidden on AlmaLinux?
Nine times out of ten on RHEL/AlmaLinux this is SELinux on a custom document root. Check with sudo ausearch -m avc -ts recent or sudo tail /var/log/audit/audit.log, then fix the label with the semanage fcontext + restorecon commands above.
Verify your work¶
# 1. Service is enabled and running
systemctl is-enabled nginx && systemctl is-active nginx
# 2. Configuration is valid
sudo nginx -t
# 3. Firewall allows HTTP/HTTPS
sudo firewall-cmd --list-services | grep -E 'http'
# 4. The site responds with your content
curl -H 'Host: example.com' http://localhost/
# -> <h1>Hello from nginx on AlmaLinux 9</h1>
# 5. SELinux label is correct on the document root
ls -Zd /var/www/example.com
# -> ...:httpd_sys_content_t:...
Summary¶
- Install with
dnf install nginx(RHEL/AlmaLinux) orapt install nginx(Debian), thensystemctl enable --now nginx. - Open the firewall with
firewall-cmd --add-service=http --add-service=https --permanent && firewall-cmd --reload. - Site config lives in
/etc/nginx/conf.d/*.confon RHEL, orsites-available+sites-enabledsymlinks on Debian. - A
server {}block needslisten,server_name,root,index, and at least onelocation. - For custom roots on RHEL/AlmaLinux, set the SELinux context with
semanage fcontext+restorecon. - Always run
nginx -tbeforesystemctl reload nginx; troubleshoot via/var/log/nginx/.
Next: serve dynamic content with PHP & PHP-FPM, host several sites in Virtual Hosts, or add TLS in HTTPS with Let's Encrypt.