Installing Nginx on CentOS / RHEL #

The Enterprise Linux ecosystem — spanning Red Hat Enterprise Linux (RHEL), CentOS Stream, Rocky Linux, and AlmaLinux — is widely known for its robustness, security, and stability in running large-scale business applications. All these distributions share the same package management foundation, using yum on legacy systems (like RHEL/CentOS 7) and dnf on modern systems (RHEL 8/9, Rocky Linux, AlmaLinux).

Unlike the Debian/Ubuntu family, which includes the Nginx package in its main repositories by default, in the RHEL ecosystem you need to take an extra step by adding a third-party repository like EPEL (Extra Packages for Enterprise Linux) or configuring the official Nginx.org repository. Additionally, the high-level security features enabled by default on RHEL — such as SELinux and Firewalld — require special configuration so Nginx can work properly without triggering 403 Forbidden or 502 Bad Gateway errors. This article guides you through a secure and comprehensive installation process.

Understanding Package Sources: EPEL vs the Official Nginx Repository #

Before starting the installation, you need to evaluate the two main Nginx package sources for Enterprise Linux. This choice will affect the stability and features available on your server.

1. EPEL (Extra Packages for Enterprise Linux) #

EPEL is a community-based repository project managed by the Fedora Special Interest Group. It provides additional high-quality software packages not included in RHEL’s core repositories.

  • Pros: Good dependency integration with Enterprise Linux distributions, maintained by the trusted Fedora community, and security updates aligned with the main distribution’s lifecycle.
  • Cons: The available Nginx version is usually not the hottest upstream release. It’s a stable version tested for long-term compatibility.

2. The Official Nginx.org Repository #

The official repository is maintained directly by the Nginx developers (F5). It’s the best choice if you want full control over the Nginx version.

  • Pros: Provides the latest Stable and Mainline versions instantly, very consistent across servers, and supports the newest web protocol features.
  • Cons: Requires manually creating a repository configuration file in /etc/yum.repos.d/.

Method 1: Installation Using the EPEL Repository #

Installing via EPEL is the fastest choice for servers that prioritize ease of operations.

On CentOS Stream 8/9, Rocky Linux, and AlmaLinux (Using DNF) #

Run the following command sequence to install EPEL first, then install Nginx:

# Install the EPEL repository into the system
sudo dnf install epel-release -y

# Update the package index to include the new repository
sudo dnf makecache

# Install the Nginx package
sudo dnf install nginx -y

On CentOS 7 and RHEL 7 (Using YUM) #

For those managing legacy version-7-based systems, the commands use yum:

# Install the EPEL repository
sudo yum install epel-release -y

# Install Nginx
sudo yum install nginx -y

After installation completes, you can verify the version you got:

nginx -v
# Output varies by distro release, usually showing the distro's stable version

Method 2: Installation Using the Official Nginx.org Repository #

To get the latest stable version straight from upstream, you need to register the official Nginx repository manually in your package manager’s configuration folder.

Step 1: Creating the Repository Configuration File #

Create a new file named nginx.repo in the /etc/yum.repos.d/ directory:

sudo vi /etc/yum.repos.d/nginx.repo

Fill the file with the following configuration. If you’re using genuine RHEL (not Rocky Linux, AlmaLinux, or CentOS), replace the word centos in the baseurl parameter with rhel:

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[!NOTE] The module_hotfixes=true parameter is very important on RHEL 8 and 9-based systems. It tells dnf to allow external Nginx packages to override the OS’s built-in module streams that may share the same package names.

Step 2: Executing the Installation #

After saving the repository file, run the installation command using dnf or yum:

# For CentOS Stream / Rocky / AlmaLinux (modern):
sudo dnf makecache
sudo dnf install nginx -y

# For RHEL / CentOS 7 (legacy):
sudo yum makecache
sudo yum install nginx -y

If you want to use the Mainline version (latest features), you need to disable the stable repository and enable the mainline one first:

# Using dnf-config-manager (needs the dnf-plugins-core package)
sudo dnf install -y dnf-plugins-core
sudo dnf-config-manager --disable nginx-stable
sudo dnf-config-manager --enable nginx-mainline

# Run update/install
sudo dnf install nginx -y

Starting and Enabling the Nginx Service #

Unlike Debian/Ubuntu-based systems, which often start Nginx right after the installation finishes, on the RHEL/CentOS ecosystem new services are installed in an inactive (inactive/dead) state and don’t automatically run after a reboot.

You must enable and start it manually using systemd:

# Start the Nginx service immediately
sudo systemctl start nginx

# Configure Nginx to start automatically when the server boots
sudo systemctl enable nginx

# Check the service's active status
sudo systemctl status nginx
# Make sure the status shows "active (running)"

Configuring the Firewall with Firewalld #

The Enterprise Linux family uses firewalld as its built-in firewall manager, not UFW. Firewalld manages traffic based on zones. By default, the public zone is active and closes all incoming ports except SSH (22).

You need to permanently allow the HTTP (80) and HTTPS (443) ports in the public zone so the web server is reachable from outside.

# Allow port 80 (HTTP) permanently
sudo firewall-cmd --permanent --add-service=http

# Allow port 443 (HTTPS) permanently
sudo firewall-cmd --permanent --add-service=https

# Apply the new configuration by reloading the firewall
sudo firewall-cmd --reload

# Verify that HTTP and HTTPS services are allowed
sudo firewall-cmd --list-services
# Example output: dhcpv6-client http https ssh

If you want to test Nginx on a custom port (e.g., port 8080), use the following commands to open a specific TCP port:

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

SELinux: Solving 403 and 502 Errors #

Security-Enhanced Linux (SELinux) is a kernel-level security mechanism enabled by default on CentOS/RHEL. SELinux restricts what a process may access based on very strict security policies.

For system administrators newly migrating to CentOS/RHEL, SELinux is the main cause of confusing errors. The two most commonly encountered error scenarios are:

  1. 403 Forbidden Error: Happens when you move the web root directory to a custom location (e.g., /home/user/web/), but the kernel denies Nginx access even though the file permissions (chmod/chown) are correct.
  2. 502 Bad Gateway Error: Happens when you configure Nginx as a reverse proxy to a backend application (like PHP-FPM on port 9000 or Node.js on port 3000), but the kernel denies the network socket connection.

Here’s the systematic decision flow of how SELinux evaluates access requests from the Nginx process:

flowchart TD
    Req["Nginx sends a resource access request"] --> Q_Type{"Resource Type?"}
    
    Q_Type -->|"File / Directory (Web Root)"| Q_FileCtx{"Does the file have <br> httpd_sys_content_t context?"}
    Q_FileCtx -->|"Yes"| AllowFile["File Access ALLOWED"]
    Q_FileCtx -->|"No"| DenyFile["File Access DENIED <br> -> HTTP 403 Forbidden"]
    
    Q_Type -->|"Network Socket (Reverse Proxy)"| Q_Bool{"Is the boolean <br> httpd_can_network_connect = 1?"}
    Q_Bool -->|"Yes"| AllowNet["Proxy Connection ALLOWED"]
    Q_Bool -->|"No"| DenyNet["Proxy Connection DENIED <br> -> HTTP 502 Bad Gateway"]
    
    style AllowFile stroke:#388e3c,stroke-width:2px
    style AllowNet stroke:#388e3c,stroke-width:2px
    style DenyFile stroke:#d32f2f,stroke-width:2px
    style DenyNet stroke:#d32f2f,stroke-width:2px

1. Fixing 403 Forbidden (File Context Configuration) #

SELinux identifies files by their security context labels. Nginx (running in the httpd_t process domain) is only allowed to read files labeled with the web server security context, httpd_sys_content_t.

If you place web files in a custom directory like /data/www/, that directory may have a default context label like default_t or user_home_t, which Nginx isn’t allowed to read.

To fix this permanently, you must change the directory’s context label:

# 1. Register the custom directory pattern in SELinux policy as httpd_sys_content_t web content type
sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"

# 2. Apply the label change to the file system recursively
sudo restorecon -R -v /data/www

If those web files need to be written by Nginx (for example, a WordPress media upload folder), you must give them a writeable context label:

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/data/www/uploads(/.*)?"
sudo restorecon -R -v /data/www/uploads

2. Fixing 502 Bad Gateway (SELinux Boolean Configuration) #

By default, SELinux forbids the web server process (httpd_t) from making outbound network connections to minimize the risk if the web server is compromised. However, when Nginx acts as a reverse proxy, it must connect to upstream backends via TCP/IP.

You need to allow this function by changing the SELinux boolean variable httpd_can_network_connect to active (1 or on):

# Enable outbound network connection permission permanently (-P flag)
sudo setsebool -P httpd_can_network_connect 1

This step will immediately resolve the 502 Bad Gateway error caused by the SELinux kernel block.

[!WARNING] It is highly discouraged to turn off SELinux by changing its configuration to disabled or permissive (using setenforce 0) on production servers. SELinux is one of the best defense systems preventing attackers from escalating privileges if your web server is compromised. Configuring SELinux correctly is far safer than disabling it.

3. Analyzing SELinux Audit Logs and Creating Custom Policies (audit2allow) #

When SELinux blocks an Nginx action, the denial message is recorded in depth in the OS audit log file, usually located at /var/log/audit/audit.log. Deleting or ignoring this log is not wise. Instead, you can analyze those specific error messages to understand which system operation was denied.

SELinux denial messages (known as AVC messages - Access Vector Cache) typically have a structure like this: type=AVC msg=audit(1686381234.567:123): avc: denied { name_connect } for pid=1234 comm="nginx" dest=8081 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket permissive=0

The message above shows that the Nginx process (with the httpd_t context) was denied when trying to make an outbound connection (name_connect) to TCP port 8081 (which has the default port_t context).

If you run a backend application on a non-standard port not defined in SELinux’s built-in boolean policies, and you don’t want to enable an overly permissive global boolean, you can create a custom SELinux policy module.

The way to do it is using the ausearch and audit2allow utilities:

# 1. Search for recent Nginx-related AVC denial messages
sudo ausearch -m AVC -ts recent | grep nginx

# 2. Create a draft custom policy module (e.g., named 'my-nginx-port') from the audit log
sudo ausearch -c 'nginx' --raw | audit2allow -M my-nginx-port

# The command above produces two files in your current directory:
# - my-nginx-port.te: A Type Enforcement text file containing the proposed rules
# - my-nginx-port.pp: A binary policy module ready to be installed into the kernel

# 3. Review the .te text file before installing to make sure there are no new security holes
cat my-nginx-port.te

# 4. Install the binary policy module into the SELinux kernel
sudo semodule -i my-nginx-port.pp

With this method, you grant Nginx specific permission to access that particular port without reducing SELinux protection on other aspects of the operating system.


Hardening Nginx Security with Systemd Sandboxing #

On modern Enterprise Linux distributions using systemd, you can apply an additional layer of security protection directly at the OS process manager level. This technique is called systemd sandboxing, which restricts the file access rights and hardware interactions Nginx can perform at the kernel level.

You can create a special systemd override file for Nginx without touching the distro package’s default configuration file:

# Open the systemd override editor for Nginx
sudo systemctl edit nginx

Systemctl will open a blank text editor. You can insert the following security hardening configuration inside the [Service] block:

### Override file: /etc/systemd/system/nginx.service.d/override.conf
[Service]
# Restrict write access to the main system directories
ProtectSystem=strict

# Allow Nginx to write ONLY to the web, log, and runtime cache directories it needs
ReadWritePaths=/var/log/nginx /var/cache/nginx /var/run /usr/share/nginx/html

# Secure user home directories from Nginx access
ProtectHome=true

# Prevent Nginx from creating special device files (like loopback or raw devices)
PrivateDevices=true

# Limit the special kernel capabilities Nginx is allowed to run
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

After you save the override file, systemd will reload its configuration automatically. This sandboxing step ensures that if an Nginx worker process is successfully compromised by an attacker, the attacker’s room to maneuver will be strictly limited inside your operating system — they won’t be able to read other users’ home folders, modify system configuration files in /etc/, or create malicious device files in /dev/.


Nginx Directory Structure on CentOS/RHEL #

After installation completes, you should note the Nginx directory layout on CentOS/RHEL, which differs slightly from Ubuntu:

  • /etc/nginx/: The main configuration directory. The nginx.conf file on CentOS/RHEL loads the /etc/nginx/conf.d/*.conf configuration folder directly. The sites-available/ and sites-enabled/ convention structure is not used by default here.
  • /usr/share/nginx/html/: This is the default Document Root on CentOS/RHEL, not /var/www/html/ as on Ubuntu/Debian. If you don’t change the root configuration, put your HTML files in this directory.
  • /var/log/nginx/: The directory for access log (access.log) and error log (error.log) files.

Summary #

  • Enable EPEL or create the /etc/yum.repos.d/nginx.repo file to install Nginx on CentOS/RHEL.
  • Remember to always run systemctl start nginx && systemctl enable nginx after installation completes, because Nginx doesn’t start automatically like it does on Ubuntu.
  • Open web access in the firewall with the command firewall-cmd --permanent --add-service={http,https} then run firewall-cmd --reload.
  • Fix the 502 Bad Gateway error caused by SELinux by running setsebool -P httpd_can_network_connect 1.
  • Fix the 403 Forbidden error on custom directories by changing the file security context label to httpd_sys_content_t using semanage fcontext and restorecon.
  • Use /usr/share/nginx/html as the default static web document location on the Enterprise Linux family.

← Previous: Ubuntu / Debian   Next: Docker →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact