You installed Nginx, but the site won't load
You installed Nginx on Fedora, started the service, and opened your browser. The connection times out. Or you get a 403 Forbidden error. You check the file permissions and they look correct. You check the firewall and port 80 is open. The error log says Permission denied. You are staring at a running service that refuses to serve anything. The issue is rarely Nginx itself. The issue is the interaction between Nginx, firewalld, and SELinux. Fedora enforces strict security boundaries. You have to configure those boundaries explicitly.
What is actually happening
Nginx runs as a systemd service. The service starts the nginx binary, which forks worker processes. These workers listen on network sockets. By default, Fedora blocks incoming traffic on port 80. firewalld sits between the network and the socket. If the firewall rule is missing, the packet never reaches Nginx. The connection times out.
SELinux sits between the process and the filesystem. Nginx runs in a confined domain. It can only access files labeled with specific contexts. If you create a directory in /var/www without labeling it, SELinux denies access. The denial appears as a 403 error. The browser sees the error. The server logs the denial. The fix requires configuring the firewall and labeling the files.
Fedora separates configuration from package files. Configuration lives in /etc/. Package files live in /usr/lib/. Updates overwrite /usr/lib/. They preserve /etc/. Edit files in /etc/. Never edit files in /usr/lib/.
Enable the service immediately. A server that does not start on boot is a server you will forget to start.
Install and enable the service
Install Nginx from the default repositories. Fedora includes the mainline version in the base repos. You do not need EPEL.
sudo dnf install nginx -y # Install nginx and dependencies from the base repository
sudo systemctl enable --now nginx # Enable the unit to start on boot and start it immediately
Run dnf upgrade --refresh weekly. This command forces a metadata refresh before upgrading packages. It prevents stale repository data from causing dependency conflicts during updates.
Open the firewall
Open the firewall for HTTP traffic. Fedora's default zone blocks incoming connections unless you explicitly allow them.
sudo firewall-cmd --permanent --add-service=http # Add the http service to the permanent zone rules
sudo firewall-cmd --reload # Reload the firewall to apply permanent rules to the runtime configuration
Always run firewall-cmd --reload after modifying permanent rules. The runtime configuration does not update automatically. Without the reload, your changes persist across reboots but do not take effect until the next restart.
Reload the firewall. Rules do not apply until you reload.
Configure the server block
Create a server block for your application. Nginx reads configuration from /etc/nginx/nginx.conf and includes files from /etc/nginx/conf.d/. The main config defines global settings. The conf.d directory holds site-specific rules. This structure keeps the config modular. You can add and remove sites by dropping files in conf.d without editing the main file.
Edit configuration files in /etc/nginx/. Files in /usr/lib/nginx/ are owned by the package. Running dnf upgrade will overwrite any changes made in /usr/lib/. Keep your customizations in /etc/ to survive package updates.
Here is how to create a server block that serves a custom directory.
server {
listen 80; # Bind to port 80 for incoming HTTP traffic
server_name example.com; # Match the Host header to this domain
location / {
root /var/www/myapp; # Set the document root for this location
index index.html; # Serve index.html if the URI matches a directory
}
}
Test the syntax. A broken config takes down the service. Test before you reload.
Fix SELinux contexts
Fix file contexts for custom web roots. SELinux uses labels to restrict access. Nginx expects web content to have the httpd_sys_content_t context. This context allows read access. If you need write access for uploads, use httpd_sys_rw_content_t.
Use semanage to set persistent SELinux contexts. The chcon command changes the label temporarily. The next restorecon run or reboot will revert chcon changes. semanage updates the policy database so the label survives reboots and filesystem relabeling.
Here is how to set the correct context and ownership for a custom directory.
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/myapp(/.*)?" # Define the SELinux context for the directory and its contents
sudo restorecon -Rv /var/www/myapp # Restore the context on existing files based on the definition
sudo chown -R nginx:nginx /var/www/myapp # Set ownership to nginx if the server needs to write to the directory
If you get a 403 error, check journalctl -t setroubleshoot. The output contains a one-line summary of the denial and often a command to fix it. Read that summary before disabling SELinux.
Label the files. Permissions are not enough on Fedora. Contexts matter.
Validate and reload
Validate the configuration and apply changes. Nginx can test its config syntax without restarting. This prevents broken configs from taking down the service.
sudo nginx -t # Test the configuration syntax and check for errors
sudo systemctl reload nginx # Reload the configuration without dropping active connections
Use reload for configuration changes. restart stops the service and starts it again, dropping active connections. reload keeps connections alive while applying the new config.
Run nginx -t first. Guessing the error wastes time. The syntax checker is instant.
Verify the installation
Check the service status and the logs. Use systemctl status nginx to see if the service is active. Use journalctl -xeu nginx to read recent logs with explanatory text. The -x flag adds explanations, and -e jumps to the end. If the service failed, the logs will show why.
sudo systemctl status nginx # Check the service state and recent log output
sudo journalctl -xeu nginx # View detailed logs with explanatory text for the nginx unit
Use journalctl -xeu nginx to debug service failures. The -x flag adds explanatory text to log entries. The -e flag jumps to the end of the journal. This command shows the error and the context in one view. Read the actual error before guessing.
Check the logs. The error message tells you exactly what went wrong. Read it.
Common pitfalls and errors
A 403 Forbidden error with correct permissions usually means SELinux is blocking access. Check journalctl -t setroubleshoot. The output contains a summary of the denial and a suggested fix. Apply the fix. Do not disable SELinux.
Nginx fails to start if the config has a syntax error. Run nginx -t before reloading. The command checks syntax and reports the line number of the error. Fix the error before reloading.
If Nginx fails to bind to port 80, another process is using the port. Run ss -tlnp | grep :80 to identify the process. Stop the conflicting service or change the Nginx port. The error looks like this:
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()
This error means something else is listening on port 80. Usually Apache or another Nginx instance. Find the process and stop it.
Trust the package manager. Manual edits drift. Snapshots stay.
When to use Nginx versus alternatives
Use Nginx when you need high performance for static content or reverse proxying with low memory usage.
Use Apache when you need .htaccess support or complex module configurations that change frequently.
Use Caddy when you want automatic HTTPS provisioning and a minimal configuration syntax.
Use Podman with Nginx when you want to isolate the web server from the host and manage it as a container.
Stick to the Fedora Nginx package when you want automatic updates and integrated SELinux policies.
Choose the tool for the job. Nginx excels at proxying. Apache excels at flexibility. Match the tool to the requirement.