NGINX.COM
Web Server Load Balancing with NGINX Plus

Editor – The blog post titled “NGINX: SELinux Changes when Upgrading to RHEL 6.6 / CentOS 6.6” redirects here. This article provides updated and generalized information.

The default settings for Security-Enhanced Linux (SELinux) on modern Red Hat Enterprise Linux (RHEL) and related distros can be very strict, erring on the side of security rather than convenience. Although the default settings do not limit the functioning of NGINX Open Source and NGINX Plus in their default configurations, other features you might configure can be blocked unless you explicitly allow them in SELinux. This article describes the possible issues and recommended ways to resolve them.

[Editor – This article applies to both NGINX Open Source and NGINX Plus. For ease of reading, the term “NGINX” is used throughout.

CentOS is a related distro originally derived from RHEL and is supported by NGINX and NGINX Plus. In addition, NGINX Plus supports the related Amazon Linux and Oracle Linux distros. Their default SELinux settings might differ from CentOS and RHEL; consult the vendor documentation.]

Overview of SELinux

SELinux is enabled by default on modern RHEL and CentOS servers. Each operating system object (process, file descriptor, file, etc.) is labeled with an SELinux context that defines the permissions and operations the object can perform. In RHEL 6.6/CentOS 6.6 and later, NGINX is labeled with the httpd_t context:

# ps auZ | grep nginx
unconfined_u:system_r:httpd_t:s0 3234 ? Ss 0:00 nginx: master process /usr/sbin/nginx \
                                                -c /etc/nginx/nginx.conf
unconfined_u:system_r:httpd_t:s0 3236 ? Ss 0:00 nginx: worker process

The httpd_t context permits NGINX to listen on common web server ports, to access configuration files in /etc/nginx, and to access content in the standard docroot location (/usr/share/nginx). It does not permit many other operations, such as proxying to upstream locations or communicating with other processes through sockets.

Temporarily Disabling SELinux for NGINX

To temporarily disable SELinux restrictions for the httpd_t context, so that NGINX can perform all the same operations as in non‑SELinux OSs, assign the httpd_t context to the permissive domain. See the next section for details.

# semanage permissive -a httpd_t

Changing SELinux Modes

SELinux can be run in enforcing, permissive, or disabled modes (also referred to as domains). Before you make an NGINX configuration change that might breach the default (strict) permissions, you can change SELinux from enforcing to permissive mode, in your test environment (if available) or production environment. In permissive mode, SELinux permits all operations, but logs operations that would have breached the security policy in enforcing mode.

To add httpd_t to the list of permissive domains, run this command:

# semanage permissive -a httpd_t

To delete httpd_t from the list of permissive domains, run:

# semanage permissive -d httpd_t

To set the mode globally to permissive, run:

# setenforce 0

To set the mode globally to enforcing, run:

# setenforce 1

Resolving SELinux Security Exceptions

In permissive mode, security exceptions are logged to the default Linux audit log, /var/log/audit/audit.log. If you encounter a problem that occurs only when NGINX is running in enforcing mode, review the exceptions that are logged in permissive mode and update the security policy to permit them.

Issue 1: Proxy Connection is Forbidden

By default, the SELinux configuration does not allow NGINX to connect to remote HTTP, FastCGI, or other servers, as indicated by an audit log message like the following:

type=AVC msg=audit(1415714880.156:29): avc:  denied  { name_connect } for  pid=1349 \
  comm="nginx" dest=8080 scontext=unconfined_u:system_r:httpd_t:s0 \
  tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1415714880.156:29): arch=c000003e syscall=42 success=no \
  exit=-115 a0=b \a1=16125f8 a2=10 a3=7fffc2bab440 items=0 ppid=1347 pid=1349 \
  auid=1000 uid=497 gid=496 euid=497 suid=497 fsuid=497 egid=496 sgid=496 fsgid=496 \
  tty=(none) ses=1 comm="nginx" exe="/usr/sbin/nginx" \
  subj=unconfined_u:system_r:httpd_t:s0 key=(null)

The audit2why command interprets the message code (1415714880.156:29):

# grep 1415714880.156:29 /var/log/audit/audit.log | audit2why
type=AVC msg=audit(1415714880.156:29): avc:  denied  { name_connect } for  pid=1349 \
  comm="nginx" dest=8080 scontext=unconfined_u:system_r:httpd_t:s0 \
  tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket
 
        Was caused by:
        One of the following booleans was set incorrectly.
        Description:
        Allow httpd to act as a relay
 
        Allow access by executing:
        # setsebool -P httpd_can_network_relay 1
        Description:
        Allow HTTPD scripts and modules to connect to the network using TCP.
 
        Allow access by executing:
        # setsebool -P httpd_can_network_connect 1

The output from audit2why indicates that you can allow NGINX to make proxy connections by enabling one or both of the httpd_can_network_relay and httpd_can_network_connect Boolean options. You can enable them either temporarily or permanently, the latter by adding the ‑P flag as shown in the output.

Understanding Boolean Options

The sesearch command provides more information about the Boolean options, and is available if you install the setools package (yum install setools). Here we show the output for the httpd_can_network_relay and httpd_can_network_connect options.

The httpd_can_network_relay Boolean Option

Here’s the output from the sesearch command about the httpd_can_network_relay option:

# sesearch -A -s httpd_t -b httpd_can_network_relay
Found 10 semantic av rules:
   allow httpd_t gopher_port_t : tcp_socket name_connect ;
   allow httpd_t http_cache_client_packet_t : packet { send recv } ;
   allow httpd_t ftp_port_t : tcp_socket name_connect ;
   allow httpd_t ftp_client_packet_t : packet { send recv } ;
   allow httpd_t http_client_packet_t : packet { send recv } ;
   allow httpd_t squid_port_t : tcp_socket name_connect ;
   allow httpd_t http_cache_port_t : tcp_socket name_connect ;
   allow httpd_t http_port_t : tcp_socket name_connect ;
   allow httpd_t gopher_client_packet_t : packet { send recv } ;
   allow httpd_t memcache_port_t : tcp_socket name_connect ;

This output indicates that httpd_can_network_relay allows processes labeled with the httpd_t context (such as NGINX) to connect to ports of various types, including type http_port_t:

# semanage port -l | grep http_port_t
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000

To add more ports (here, 8082) to the set of ports permitted for http_port_t, run:

# semanage port -a -t http_port_t -p tcp 8082

If the output from this command says that a port is already defined, as in the following example, it means the port is included in another set. Do not reassign it to http_port_t, because other services might be negatively affected.

# semanage port -a -t http_port_t -p tcp 8080
/usr/sbin/semanage: Port tcp/8080 already defined
# semanage port -l | grep 8080
http_cache_port_t              tcp      3128, 8080, 8118, 8123, 10001-10010

The httpd_can_network_connect Boolean Option

Here’s the output from the sesearch command about the httpd_can_network_connect option:

# sesearch -A -s httpd_t -b httpd_can_network_connect
Found 1 semantic av rules:
   allow httpd_t port_type : tcp_socket name_connect ;

This output indicates that httpd_can_network_connect allows processes labeled with the httpd_t context (such as NGINX) to connect to all TCP socket types that have the port_type attribute. To list them, run:

# seinfo -aport_type -x

Issue 2: File Access is Forbidden

By default, the SELinux configuration does not allow NGINX to access files outside of well‑known authorized locations, as indicated by an audit log message like the following:

type=AVC msg=audit(1415715270.766:31): avc:  denied  { getattr } for  pid=1380 \
  comm="nginx" path="/www/t.txt" dev=vda1 ino=1084 \
  scontext=unconfined_u:system_r:httpd_t:s0 \
  tcontext=unconfined_u:object_r:default_t:s0 tclass=file

The audit2why command interprets the message code (1415715270.766:31):

# grep 1415715270.766:31 /var/log/audit/audit.log | audit2why
type=AVC msg=audit(1415715270.766:31): avc:  denied  { getattr } for  pid=1380 \
  comm="nginx" path="/www/t.txt" dev=vda1 ino=1084 \
  scontext=unconfined_u:system_r:httpd_t:s0 \
  tcontext=unconfined_u:object_r:default_t:s0 tclass=file
 
    Was caused by:
        Missing type enforcement (TE) allow rule.
 
        You can use audit2allow to generate a loadable module to allow this access.

When file access is forbidden, you have two options.

Option 1: Modify the File Label

Modify the file label so that NGINX (as a process labeled with the httpd_t context) can access the file:

# chcon -v --type=httpd_sys_content_t /www/t.txt

By default, this modification is deleted when the file system is relabeled. To make the change permanent, run:

# semanage fcontext -a -t httpd_sys_content_t /www/t.txt
# restorecon -v /www/t.txt

To modify file labels for groups of files, run:

# semanage fcontext -a -t httpd_sys_content_t '/www(/.*)?'
# restorecon -Rv /www

Option 2: Extend the httpd_t Domain Permissions

Extend the policy for httpd_t to allow access to additional file locations:

# grep nginx /var/log/audit/audit.log | audit2allow -m nginx > nginx.te
# cat nginx.te
 
module nginx 1.0;
 
require {
        type httpd_t;
        type default_t;
        type http_cache_port_t;
        class tcp_socket name_connect;
        class file { read getattr open };
}
 
#============= httpd_t ==============
allow httpd_t default_t:file { read getattr open };
 
#!!!! This avc can be allowed using one of these booleans:
#     httpd_can_network_relay, httpd_can_network_connect
allow httpd_t http_cache_port_t:tcp_socket name_connect;

To generate a compiled policy, include the -M option:

# grep nginx /var/log/audit/audit.log | audit2allow -M nginx

To load the policy, run semodule -i, then verify success with semodule -l:

# semodule -i nginx.pp
# semodule -l | grep nginx
nginx 1.0

This change persists across reboots.

Issue 3: NGINX Cannot Bind to Additional Ports

By default, the SELinux configuration does not allow NGINX to listen (bind()) to TCP or UDP ports other than the default ones that are allowlisted in the http_port_t type:

# semanage  port -l | grep http_port_t
http_port_t                    tcp      80, 443, 488, 8008, 8009, 8443

If you try to configure NGINX to listen on a non‑allowlisted port (with the listen directive in the http, stream, or mail context in the NGINX configuration), you get an error when you verify (nginx -t) or reload the NGINX configuration, as indicated by this NGINX log entry:

YYYY/MM/DD hh:mm:ss [emerg] 46123#0: bind() to 0.0.0.0:8001 failed (13: Permission denied)

You can use semanage to add the desired port (here, 8001) to the http_port_t type:

# semanage port -a -t http_port_t -p tcp 8001

Reload NGINX with the new configuration.

# nginx -s reload

Issue 4: Too many files are open Error

When the limit on the number of open files (RLIMIT_NOFILE) is exceeded, the following message appears in the error log:

Too many files are open

When the NGINX Worker Process Generates the Error

In most cases, the NGINX worker process reports this error, but you cannot use the NGINX worker_rlimit_nofile directive to increase the limit because SELinux doesn’t allow the setrlimit() system call, as reported in the following messages in error and audit logs.

  • In /var/log/nginx/error.log for CentOS/RHEL 7.4+:

    YYYY/MM/DD hh:mm:ss [alert] 12066#0: setrlimit(RLIMIT_NOFILE, 2342) failed (13: Permission denied)
  • In /var/log/nginx/error.log for CentOS/RHEL 8.0+:

    YYYY/MM/DD hh:mm:ss [alert] 3327#0: setrlimit(RLIMIT_NOFILE, 65535) failed (1: Operation not permitted)
  • In /var/log/audit/audit.log for CentOS/RHEL 7.4+ and /var/log/messages for CentOS/RHEL 8.0+:

    type=AVC msg=audit(1437731200.211:366): avc:  denied  { setrlimit } for pid=12066 \
      comm="nginx" scontext=system_u:system_r:httpd_t:s0 \
      tcontext=system_u:system_r:httpd_t:s0 tclass=process

To increase the limit, instead run this command as the root user:

$ setsebool -P httpd_setrlimit 1

When the NGINX Master Process Generates the Error

If the NGINX master process reports the error, you need to update the systemd unit file for NGINX. This sets the file descriptor limit for both master and worker processes.

  1. Create a directory for the nginx.service configuration:

    $ mkdir /etc/systemd/system/nginx.service.d
  2. Add the following lines to /etc/systemd/system/nginx.service.d/nofile_limit.conf:

    [Service]
    LimitNOFILE=65535
  3. Reload the systemd daemon configuration and restart NGINX:

    $ systemctl daemon-reload
    $ systemctl restart nginx.service

Additional Resources

SELinux is a complex and powerful facility for managing operating system permissions. Additional information is available in the following documents.

Hero image
Free O'Reilly eBook: The Complete NGINX Cookbook

Updated for 2024 – Your guide to everything NGINX



About The Author

Owen Garrett

Sr. Director, Product Management

Owen is a senior member of the NGINX Product Management team, covering open source and commercial NGINX products. He holds a particular responsibility for microservices and Kubernetes‑centric solutions. He’s constantly amazed by the ingenuity of NGINX users and still learns of new ways to use NGINX with every discussion.

About F5 NGINX

F5, Inc. is the company behind NGINX, the popular open source project. We offer a suite of technologies for developing and delivering modern applications. Together with F5, our combined solution bridges the gap between NetOps and DevOps, with multi-cloud application services that span from code to customer.

Learn more at nginx.com or join the conversation by following @nginx on Twitter.