You hit 403 Forbidden after installing Nginx
You installed Nginx on Fedora, enabled the service, and pointed your browser to http://localhost. The default welcome page loads. You create a directory at /var/www/myapp, drop an index.html file in there, and update the config. You reload Nginx. You refresh the browser. You get a 403 Forbidden error. You check the file permissions with ls -l and they look fine. You check the service status and it is active. You are stuck between a working server and a security policy that refuses to serve your files.
This is the standard Fedora experience. The system is secure by default. Nginx runs with restricted privileges. The firewall blocks incoming traffic. SELinux enforces strict access controls on files. You have to explicitly grant access to everything. This workflow prevents a compromised web server from reading your SSH keys or modifying system binaries. Follow the steps below to install Nginx, open the necessary ports, configure a server block, and set the correct SELinux contexts so your content actually loads.
What is blocking your request
Nginx is a high-performance web server that runs as a systemd service. On Fedora, the nginx package installs the binary, the default configuration, and the service unit. The service runs as the nginx user, not root. This limits the damage if the process is exploited.
The firewall, managed by firewalld, blocks all incoming connections by default. Ports 80 and 443 are closed until you add the http and https services to the allowed list. Without this step, external clients will time out.
SELinux is the deeper layer. Linux permissions control who can read a file based on user and group. SELinux adds a label to every file and process. Nginx is labeled httpd_t. It can only read files labeled httpd_sys_content_t or httpd_sys_rw_content_t. If you create a new directory, it inherits the context of the parent or gets a generic label like default_t. Nginx cannot read default_t. The kernel denies the access and Nginx returns 403 Forbidden. The error log will show Permission denied, but the fix is not chmod. The fix is correcting the SELinux context.
Install and start the service
Install the package and enable the service to start on boot. Fedora's package manager handles dependencies automatically.
sudo dnf install nginx -y # Install the nginx package and all required dependencies
sudo systemctl enable --now nginx # Enable auto-start on boot and start the service immediately
Enable the service now. You do not want to debug why the server died after a reboot.
Check the service status to confirm it is running. The output shows the active state and recent log lines.
systemctl status nginx # Show the current state and recent journal entries for the service
If the service fails to start, the status output will show a red failed line and a hint about the error. Run journalctl -xeu nginx.service to see the full log. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type this muscle-memory style when a unit misbehaves.
Open the firewall
The firewall blocks external access until you open the ports. Add the HTTP and HTTPS services to the permanent configuration and reload the firewall to apply the changes.
sudo firewall-cmd --permanent --add-service=http # Add HTTP to the permanent firewall rules
sudo firewall-cmd --permanent --add-service=https # Add HTTPS to the permanent firewall rules
sudo firewall-cmd --reload # Apply the permanent rules to the running firewall
Reload the firewall. The rules do not apply until you do. If you skip the reload, the runtime configuration and the persistent configuration diverge, and you will wonder why the ports are still closed after a reboot.
Verify the ports are open. The output should list http and https in the services column.
sudo firewall-cmd --list-services # Show all services allowed in the current zone
Configure the server block
Nginx reads its configuration from /etc/nginx/nginx.conf. The main config includes files from /etc/nginx/conf.d/. You should never edit /etc/nginx/nginx.conf directly for site-specific settings. Package updates can overwrite manual changes in the main file. Create a new file in conf.d for your application.
Edit the configuration in /etc/. Never edit files in /usr/lib/. Files in /usr/lib/ ship with the package and will be replaced on upgrade. Files in /etc/ are user-modified and persist across updates.
Create a server block for your site. This example serves static files from /var/www/myapp.
server {
listen 80; # Listen on port 80 for HTTP traffic
server_name myapp.example.com; # Match requests for this domain name
location / {
root /var/www/myapp; # Serve files from this directory
index index.html; # Serve index.html if the URL ends with /
}
}
Save the file as /etc/nginx/conf.d/myapp.conf. The filename does not matter as long as it ends in .conf. Nginx loads all files in conf.d alphabetically.
Test the configuration syntax before reloading. A syntax error will prevent Nginx from reloading and can leave the server in a broken state.
sudo nginx -t # Test configuration syntax without reloading the service
If the test passes, reload Nginx to apply the changes. Reloading is graceful. It restarts worker processes without dropping existing connections.
sudo systemctl reload nginx # Reload the configuration without stopping the service
Test the config. A syntax error kills the reload.
Fix SELinux contexts
If you serve files from a custom directory, you must set the correct SELinux context. Nginx will deny access to files with the wrong label. Use semanage to define the policy permanently and restorecon to apply it.
sudo dnf install policycoreutils-python-utils -y # Install semanage and other SELinux tools
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/myapp(/.*)?" # Define the correct context for the directory
sudo restorecon -Rv /var/www/myapp # Apply the context to existing files recursively
The semanage fcontext command adds a rule to the SELinux policy database. The regex (/.*)? matches the directory and everything inside it. The restorecon command applies the rule to the filesystem. If you create new files later, restorecon will fix them. You do not need to run semanage again.
Use httpd_sys_content_t for read-only content. Use httpd_sys_rw_content_t if your application needs to write files, such as an upload directory. Never use chcon to change contexts. chcon modifies the label on disk but does not update the policy. The next restorecon run will revert your changes.
Check the context. SELinux does not care about your user permissions.
ls -Z /var/www/myapp # Show the SELinux context of the directory and files
The output should show httpd_sys_content_t for the files. If you see default_t or user_home_t, Nginx cannot read them.
Verify the setup
Verify the installation by checking the HTTP response and the logs. Use curl to request the page and inspect the headers.
curl -I http://localhost # Check HTTP headers and status code
The response should show HTTP/1.1 200 OK. If you see 403 Forbidden, check the SELinux context and the file permissions. If you see 404 Not Found, check the root directive and the filename.
Check the error log for denials. SELinux denials appear in the audit log and are summarized by the setroubleshoot service.
sudo journalctl -t setroubleshoot | tail -n 20 # Show recent SELinux denial summaries
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. Disabling SELinux is a security risk and rarely the correct fix. The denial message tells you exactly which file was blocked and what context is required.
Run journalctl first. Read the actual error before guessing.
Common errors and how to read them
Nginx returns specific errors when things go wrong. Match the error to the cause.
If you see nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use), another process is listening on port 80. This often happens if Apache is running or if a previous Nginx instance did not shut down cleanly. Find the process and stop it.
sudo ss -tlnp | grep :80 # Show which process is listening on port 80
If you see 403 Forbidden in the browser and Permission denied in the error log, check SELinux. The file permissions might be correct, but the context is wrong. Run ls -Z on the directory. If the context is not httpd_sys_content_t, fix it with semanage and restorecon.
If you see 502 Bad Gateway, the backend server is down or not responding. This error appears when Nginx is configured as a reverse proxy. Check the upstream service and the proxy configuration.
If the service fails to start and systemctl status shows exit code 1/FAILURE, run journalctl -xeu nginx.service. The logs will show the exact line in the config file that caused the error. Fix the syntax and run nginx -t again.
Choose the right web server
Fedora supports multiple web servers. Pick the one that matches your workload.
Use Nginx when you need high concurrency and low memory usage for static content or reverse proxying. Use Apache when you need .htaccess support or complex module-based configurations. Use Caddy when you want automatic HTTPS and a simpler configuration syntax. Use the default Workstation setup if you are just testing locally and do not need external access.