F5 NGINX Security Technical Implementation Guide
Pick two releases to diff their requirements.
Open a previous version of this STIG.
Supporting documents 4 PDFs
Bundled by DISA alongside this STIG release: overview, revision history, and readme files. Download the full archive or open an individual PDF.
- RMF Control
- AC-10
- Severity
- M
- CCI
- CCI-000054
- Version
- NGNX-APP-000010
- Vuln IDs
-
- V-278380
- Rule IDs
-
- SV-278380r1172745_rule
Checks: C-82914r1172691_chk
Determine path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. nginx -c <path to nginx config> -qT | grep worker_connections worker_connections 512; Verify "worker_connections" is set in the configuration to an organization-defined number. If the "worker_connections" is not set to an organization-defined number, this is a finding.
Fix: F-82819r1172744_fix
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Add or modify the number of "worker_connections" based on the needs of the application. Note: By default, NGINX limits the number of concurrent connections to 512 per worker process, and the number of worker processes is equal the number of CPUs present. This results in a maximum number of connections equal to "worker_connections" multiplied by the number of "worker_processes". In a proxy configuration, NGINX will use a connection on both the client side and the server side. This results in two connections for each client side connection. Restart NGINX: systemctl restart nginx
- RMF Control
- AC-17
- Severity
- H
- CCI
- CCI-000068
- Version
- NGNX-APP-000040
- Vuln IDs
-
- V-278381
- Rule IDs
-
- SV-278381r1171895_rule
Checks: C-82915r1171893_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify "ssl_protocols" are set to TLSv1.2 or higher: nginx -c <path to nginx config> -qT | grep ssl_protocols ssl_protocols TLSv1.2 TLSv1.3; If "ssl_protocols" does not exist or does not specify TLSv1.2 or greater, this is a finding
Fix: F-82820r1171894_fix
Specify the allowed TLS protocols by adding the following line to the server {} block: ssl_protocols TLSv1.2 TLSv1.3; Restart NGINX with saved configuration: nginx -s reload
- RMF Control
- AC-3
- Severity
- M
- CCI
- CCI-000213
- Version
- NGNX-APP-000140
- Vuln IDs
-
- V-278382
- Rule IDs
-
- SV-278382r1171898_rule
Checks: C-82916r1171896_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Identify the NGINX runtime user: grep -E '^\s*user\s+' /etc/nginx/nginx.conf Expected output (example): user nginx; Verify the user has no login shell: getent passwd nginx Expected output: nginx:x:998:998:Nginx user:/nonexistent:/sbin/nologin Ensure the shell is "/sbin/nologin", "/usr/sbin/nologin", or "/bin/false". If the NGINX runtime user has shell access, this is a finding.
Fix: F-82821r1171897_fix
Set a nonlogin shell for the nginx user: sudo usermod -s /sbin/nologin nginx
- RMF Control
- AC-4
- Severity
- M
- CCI
- CCI-001368
- Version
- NGNX-APP-000150
- Vuln IDs
-
- V-278383
- Rule IDs
-
- SV-278383r1171901_rule
Checks: C-82917r1171899_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Identify the NGINX runtime user: grep -E '^\s*user\s+' /etc/nginx/nginx.conf Expected output (example): user nginx; Ensure the user is not in a privileged group: id nginx Expected output: uid=980(nginx) gid=979(nginx) groups=979(nginx) The user should not be a member of sudo, wheel, admin, or similar elevated groups. If the NGINX runtime user is a member of an elevated group, this is a finding.
Fix: F-82822r1171900_fix
Remove the user from privileged groups: sudo gpasswd -d nginx sudo sudo gpasswd -d nginx wheel sudo gpasswd -d nginx admin
- RMF Control
- AC-8
- Severity
- M
- CCI
- CCI-000048
- Version
- NGNX-APP-000180
- Vuln IDs
-
- V-278384
- Rule IDs
-
- SV-278384r1171904_rule
Checks: C-82918r1171902_chk
Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Check nginx.conf for a section that verifies that user consent has been given. Setting a cookie upon user acceptance after reading the banner and checking for the presence of that cookie is one way to accomplish this. Check for this block in nginx.conf under the http block: # Define whether consent has been given based on the cookie map $http_cookie $consent_given { "~*user_consent=1" 1; default 0; } Check nginx.conf for a section that handles user consent and setting the cookie under the server block: # Serve the consent banner page if consent is not given location /consent { default_type text/html; return 200 "<html><body> <h1>Consent Required</h1> <p>You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions: -The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations. -At any time, the USG may inspect and seize data stored on this IS. -Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose. -This IS includes security measures (e.g., authentication and access controls) to protect USG interests--not for your personal benefit or privacy. -Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.</p> <a href='/set-consent'>I Accept</a> </body></html>"; } # Handle consent acceptance and set the cookie location /set-consent { add_header Set-Cookie "user_consent=1; Path=/; Max-Age=31536000; HttpOnly"; return 302 /; } location / { # Redirect users to the consent page if they haven't given consent if ($consent_given = 0) { return 302 /consent; } If NGINX is not configured to display the Standard Mandatory DOD Notice and Consent Banner before granting access to the application, this is a finding.
Fix: F-82823r1171903_fix
Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Configure nginx.conf for a section that verifies that user consent has been given. Setting a cookie upon user acceptance after reading the banner and checking for the presence of that cookie is one way to accomplish this. Add this block in nginx.conf under the http block: # Define whether consent has been given based on the cookie map $http_cookie $consent_given { "~*user_consent=1" 1; default 0; } Configure nginx.conf for a section that handles user consent and setting the cookie under the server block: # Serve the consent banner page if consent is not given location /consent { default_type text/html; return 200 "<html><body> <h1>Consent Required</h1> <p>You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions: -The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations. -At any time, the USG may inspect and seize data stored on this IS. -Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose. -This IS includes security measures (e.g., authentication and access controls) to protect USG interests--not for your personal benefit or privacy. -Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.</p> <a href='/set-consent'>I Accept</a> </body></html>"; } # Handle consent acceptance and set the cookie location /set-consent { add_header Set-Cookie "user_consent=1; Path=/; Max-Age=31536000; HttpOnly"; return 302 /; } location / { # Redirect users to the consent page if they haven't given consent if ($consent_given = 0) { return 302 /consent; } Save nginx.conf, test the config, and reload NGINX: nginx -t && nginx -s reload
- RMF Control
- AU-12
- Severity
- M
- CCI
- CCI-000169
- Version
- NGNX-APP-000240
- Vuln IDs
-
- V-278385
- Rule IDs
-
- SV-278385r1171907_rule
Checks: C-82919r1171905_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. cat <path to config> Verify that $server_name, $server_addr, $remote_addr, $remote_user, $time_local, $status, $request, $request_id, $http_user_agent, $http_x_forwarded_for, and/or any organization defined variables are included in any custom log_format directive. If a custom log_format is defined and does not include a minimum of $server_name, $server_addr, $remote_addr, $remote_user, $time_local, $status, $request, $request_id, $http_user_agent, and $http_x_forwarded_for, this is a finding.
Fix: F-82824r1171906_fix
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Include the $server_name, $server_addr, $remote_addr, $remote_user, $time_local, $status, $request, $request_id, $http_user_agent, $http_x_forwarded_for, or any organization defined variable in any custom log_format directives. For example: log_format custom '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; Restart NGINX: nginx -s reload
- RMF Control
- AU-12
- Severity
- M
- CCI
- CCI-000171
- Version
- NGNX-APP-000250
- Vuln IDs
-
- V-278386
- Rule IDs
-
- SV-278386r1171910_rule
Checks: C-82920r1171908_chk
Check the current permissions of nginx.conf: ls -l /etc/nginx/nginx.conf -rw-r--r-- 1 root root 0 May 23 15:04 /etc/nginx/nginx.conf If file has write permissions for anyone other than the owner, this is a finding.
Fix: F-82825r1171909_fix
By default, nginx.conf has file permissions set to admins only. Performing the chmod command will set file permissions on nginx.conf. sudo chmod 600 /etc/nginx/nginx.conf This example command will set read/write permissions for the owner only.
- RMF Control
- SC-18
- Severity
- M
- CCI
- CCI-001695
- Version
- NGNX-APP-000370
- Vuln IDs
-
- V-278387
- Rule IDs
-
- SV-278387r1172701_rule
Checks: C-82921r1172700_chk
Check nginx.conf for external modules being loaded (grep load_module). If additional modules are being loaded, confirm the directory does not include write or execute for other users. Determine the path to nginx config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. # grep load_module /etc/nginx/nginx.conf load_module modules/ngx_http_app_protect_module.so; # ls -la /etc/nginx/modules lrwxrwxrwx 1 root root 22 Oct 10 2023 modules -> /usr/lib/nginx/modules # ls -la /usr/lib/nginx drwxr-xr-x root root 4096 Jan 30 2024 modules If directory where modules are loaded is writeable by other, this is a finding.
Fix: F-82826r1171912_fix
Set permissions on directory containing external modules to read only for "Other" only. The directory may be organizationally defined. The default path is /usr/lib/nginx/modules. # chmod o-wx /usr/lib/nginx/modules
- RMF Control
- AU-9
- Severity
- M
- CCI
- CCI-000162
- Version
- NGNX-APP-000400
- Vuln IDs
-
- V-278388
- Rule IDs
-
- SV-278388r1171916_rule
Checks: C-82922r1171914_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Determine the location of the access and error logs: grep "_log" <path to config> Determine the permissions for the log files: ls -la <path to error.log> -rw-r--r-- 1 root root 0 May 23 15:04 /var/log/nginx/error.log ls -la <path to access.log> -rw-r--r-- 1 root root 0 May 23 15:04 /var/log/nginx/access.log If files have write permissions for anyone other than the owner, this is a finding.
Fix: F-82827r1171915_fix
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Determine the location of the access and error logs: grep "_log" <path to config> Set appropriate permissions for the log files: chmod 644 <path to error.log> chmod 644 <path to access.log>
- RMF Control
- CM-7
- Severity
- M
- CCI
- CCI-000382
- Version
- NGNX-APP-000510
- Vuln IDs
-
- V-278389
- Rule IDs
-
- SV-278389r1172704_rule
Checks: C-82923r1171917_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Review the configurations looking for any listen directives. If listen directives are enabled but unnecessary, this is a finding. Verify that listeners are using SSL and redirects to SSL-enabled listeners. # nginx -c <path to nginx config> -qT | grep -A5 listen listen 192.168.0.254:80; return 301 https://$host/$request_uri; } ` listen 192.168.0.254:443 ssl default_server; ... If the listen directive does not include SSL and there is not a redirect to an SSL listener, this is a finding.
Fix: F-82828r1171918_fix
Edit the NGINX configuration file(s) and remove any unnecessary listen directives. On listen directives that are organizationally defined to need TLS, ensure that the listen directive includes SSL and SSL redirection. After saving the configuration, reload NGINX: # nginx -s reload
- RMF Control
- IA-2
- Severity
- M
- CCI
- CCI-001941
- Version
- NGNX-APP-000590
- Vuln IDs
-
- V-278390
- Rule IDs
-
- SV-278390r1172747_rule
Checks: C-82924r1171920_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Identify authentication mechanism in use by checking whether NGINX is configured to protect access to administrative interfaces or APIs: grep -Ri 'auth_' /etc/nginx/nginx.conf grep -Ri 'proxy_pass' /etc/nginx/nginx.conf grep -Ri 'ssl_verify_client' /etc/nginx/nginx.conf Also inspect references to: auth_jwt auth_request ssl_client_certificate If JWT is in use, validate the config: auth_jwt_key_file /etc/nginx/jwt.pub; auth_jwt_require exp iat; Ensure the token includes expiration (exp) and ideally issued-at (iat) fields. If using mutual TLS: ssl_verify_client on; ssl_client_certificate /etc/nginx/certs/ca.pem; Ensure client-side certificate verification is required and the certificate authority (CA) trust is defined. If using auth_request: Ensure the upstream authentication server is enforcing replay resistance (such as nonce or short-lived tokens). Validate token behavior and session timeout logic. If no replay-resistant mechanism is found for network-based access, this is a finding.
Fix: F-82829r1172746_fix
Implement a replay-resistant authentication method for any NGINX Plus resource that allows network access. Option A: JWT with expiration: Configure NGINX to use JWT authentication: auth_jwt "Protected"; auth_jwt_key_file /etc/nginx/keys/jwt_key.pub; auth_jwt_require exp iat; Ensure the token issuer includes a short-lived exp (e.g., less than five minutes) and iat claims. Option B: Mutual TLS: Enable client certificate authentication: ssl_verify_client on; ssl_client_certificate /etc/nginx/certs/client_ca.pem;
- RMF Control
- IA-5
- Severity
- M
- CCI
- CCI-000185
- Version
- NGNX-APP-000720
- Vuln IDs
-
- V-278391
- Rule IDs
-
- SV-278391r1171925_rule
Checks: C-82925r1171923_chk
If using OCSP for certificate revocation, this requirement is Not Applicable. Determine the path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. # cat <path to config> Check the http { blocks for the following example: http { server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client on; ssl_crl /etc/nginx/ssl/crl.pem; ssl_ocsp on; ssl_ocsp_responder http://ocsp.disa.mil; ssl_stapling on; ssl_stapling_verify on; ssl_stapling_file /etc/nginx/ssl/ocsp_cache.pem; ssl_stapling_responder_timeout 3s; # Timeout for OCSP responder queries ssl_stapling_responder_error_cache_time 300s; # Cache time for responder errors location / { proxy_pass http://backend; } } } Check for certificate path validation. If "ssl_verify_client on" is not present in the configuration, this is a finding. Check if a CRL file is configured. If "ssl_crl <file>" is not present in the configuration, this is a finding.
Fix: F-82830r1171924_fix
If using OCSP for certificate revocation, this requirement is Not Applicable. Determine path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. # cat <path to config> Configure the following lines in the http { blocks using the example below. Set ssl_verify_client on. Set ssl_crl /etc/nginx/ssl/crl.pem to match the CRL file name. http { server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client on; ssl_crl /etc/nginx/ssl/crl.pem; ssl_ocsp on; ssl_ocsp_responder http://ocsp.disa.mil; ssl_stapling on; ssl_stapling_verify on; ssl_stapling_file /etc/nginx/ssl/ocsp_cache.pem; ssl_stapling_responder_timeout 3s; # Timeout for OCSP responder queries ssl_stapling_responder_error_cache_time 300s; # Cache time for responder errors location / { proxy_pass http://backend; } } }
- RMF Control
- IA-5
- Severity
- M
- CCI
- CCI-000186
- Version
- NGNX-APP-000730
- Vuln IDs
-
- V-278392
- Rule IDs
-
- SV-278392r1171928_rule
Checks: C-82926r1171926_chk
Determine the path to NGINX config file: nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. cat <path to config> Verify that private key(s) are only readable by the owner. Example: root@e4a935153ece:/etc/ssl/nginx# nginx -qT | grep certificate_key ssl_certificate_key /etc/ssl/nginx/server.key; root@e4a935153ece:/etc/ssl/nginx# ls -la /etc/ssl/nginx/server.key -rw------- 1 root root 1704 Dec 4 18:31 /etc/ssl/nginx/server.key If the private key(s) are readable anyone other than owner, this is a finding.
Fix: F-82831r1171927_fix
Change permissions on any TLS keys used in NGINX configuration: nginx -qT | grep certificate_key chmod 600 <path to TLS key> Example: root@e4a935153ece:/etc/ssl/nginx# nginx -qT | grep certificate_key ssl_certificate_key /etc/ssl/nginx/server.key; root@e4a935153ece:/etc/ssl/nginx# chmod 600 /etc/ssl/nginx/server.key Restart NGINX: nginx -s reload
- RMF Control
- SC-18
- Severity
- M
- CCI
- CCI-001166
- Version
- NGNX-APP-000850
- Vuln IDs
-
- V-278393
- Rule IDs
-
- SV-278393r1171931_rule
Checks: C-82927r1171929_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. # grep load_module /etc/nginx/nginx.conf load_module modules/ngx_http_app_protect_module.so; If modules are loaded that are not required or known, this is a finding.
Fix: F-82832r1171930_fix
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Remove any unnecessary or unpermitted modules from the configuration. After saving the configuration, reload NGINX: # nginx -s reload
- RMF Control
- SC-5
- Severity
- M
- CCI
- CCI-001094
- Version
- NGNX-APP-001030
- Vuln IDs
-
- V-278394
- Rule IDs
-
- SV-278394r1171934_rule
Checks: C-82928r1171932_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify timeouts and send timeouts exist in config file and the timeout value of 10 seconds (or less) has been configured for client headers and body. nginx -c <path to nginx config> -qT | grep timeout client_body_timeout 10s; client_header_timeout 10s; send_timeout 10s; If the client_header_timeout, client_body_timeout and send_timeout are unset or have values greater than 10, this is a finding.
Fix: F-82833r1171933_fix
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Add or modify the client_body_timeout, client_header_timeout and send_timeout directives to have a value of 10 seconds or lower. Setting this value in the http context will cover everything unless overridden in subsequent server or location contexts. client_body_timeout 10s; client_header_timeout 10s; send_timeout 10s; After saving the configuration, reload NGINX: # nginx -s reload
- RMF Control
- SI-11
- Severity
- M
- CCI
- CCI-001312
- Version
- NGNX-APP-001070
- Vuln IDs
-
- V-278395
- Rule IDs
-
- SV-278395r1172748_rule
Checks: C-82929r1171935_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify that the "server_tokens" directive is present, is not set to "on", and is not set to a custom string that identifies version information. nginx -c <path to nginx config> -qT | grep server_tokens server_tokens off; If the "server_tokens" directive is missing, this is a finding. If the "server_tokens" directive is set to "on", this is a finding. If the "server_tokens" directive includes the version number, this is a finding.
Fix: F-82834r1171936_fix
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Add or modify the "server_tokens" directive to set to "off" or set to a custom string without the version information. http { server_tokens off; ... } Restart nginx after modifying the configuration: # nginx -s reload
- RMF Control
- AU-4
- Severity
- H
- CCI
- CCI-001851
- Version
- NGNX-APP-001400
- Vuln IDs
-
- V-278396
- Rule IDs
-
- SV-278396r1172699_rule
Checks: C-82930r1171938_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify that "syslog:server= prefix" is included in any log directive: # cat <path to config> Find the "error_log: or "access_log" directives and verify the syslog:server= prefix is included. If "error_log" or "access_log" exists and does not include "syslog:server=", this is a finding.
Fix: F-82835r1172698_fix
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Include the "syslog:server= prefix" (which can be a domain name, an IP address, or a UNIX-domain socket path. A domain name or IP address can be specified with a port to override the default port, 514. A UNIX-domain socket path can be specified after the unix: prefix:) in any log directives and configure the optional parameters (facility, tag, severity). After saving the configuration, reload NGINX: # nginx -s reload
- RMF Control
- CM-5
- Severity
- M
- CCI
- CCI-001813
- Version
- NGNX-APP-001590
- Vuln IDs
-
- V-278397
- Rule IDs
-
- SV-278397r1171943_rule
Checks: C-82931r1171941_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Check the permissions on the directory: # ls -la /etc drwxr-x-r-x 3 root root 4096 Sep 16 18:28 nginx If permissions to write are allowed for "Other", this is a finding.
Fix: F-82836r1171942_fix
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Ensure permissions on the configuration directory do not allow write permissions for "Other": # chmod o-w /etc/nginx
- RMF Control
- CM-7
- Severity
- M
- CCI
- CCI-001774
- Version
- NGNX-APP-001630
- Vuln IDs
-
- V-278398
- Rule IDs
-
- SV-278398r1171946_rule
Checks: C-82932r1171944_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify allow/deny is set according to organizational policy: location / { allow 192.168.0.0; allow 10.0.0.0/16; deny all; } If allow or deny is not set to organizational policy, this is a finding.
Fix: F-82837r1171945_fix
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Set allow/deny according to organizational policy. Restart NGINX after modifying the configuration. # nginx -s reload
- RMF Control
- IA-11
- Severity
- M
- CCI
- CCI-002038
- Version
- NGNX-APP-001640
- Vuln IDs
-
- V-278399
- Rule IDs
-
- SV-278399r1172775_rule
Checks: C-82933r1172775_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Examine the SSL configuration settings: grep -R 'ssl_' /etc/nginx/nginx.conf Verify that "ssl_session_timeout" is not set to greater than 15. If "ssl_session_timeout" directive is missing or set to greater than 15, this is a finding.
Fix: F-82838r1171948_fix
Set the ssl_session_timeout directive to 15 or less. Note: The default setting is five minutes and meets the control, but this STIG explicitly sets the variable to not rely on a default which may change in future versions.
- RMF Control
- IA-2
- Severity
- M
- CCI
- CCI-001953
- Version
- NGNX-APP-001650
- Vuln IDs
-
- V-278400
- Rule IDs
-
- SV-278400r1172752_rule
Checks: C-82934r1171950_chk
Determine path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Check that the nginx.conf file has the SSL Certificate/Key installed, the SSL Client Certificate is present, and SSL Verify is configured. server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server_cert.pem; ssl_certificate_key /etc/nginx/ssl/server_key.pem; # Enable client certificate verification ssl_client_certificate /etc/nginx/ca_cert.pem; ssl_verify_client on; # Optional: Set verification depth for client certificates ssl_verify_depth 2; location / { proxy_pass http://backend_service; # Restrict access to valid PIV credentials if ($ssl_client_verify != SUCCESS) { return 403; } } } If the certificates are not configured and ssl_verify is not enabled, this is a finding.
Fix: F-82839r1172751_fix
NGINX installs OpenSSL by default. If not installed, follow the OS documentation. Include the following lines in the server {} block of nginx.conf: ssl_certificate /etc/nginx/ssl/server_cert.pem; ssl_certificate_key /etc/nginx/ssl/server_key.pem; # Enable client certificate verification ssl_client_certificate /etc/nginx/ca_cert.pem; ssl_verify_client on; # Optional: Set verification depth for client certificates ssl_verify_depth 2; location / { proxy_pass http://backend_service; # Restrict access to valid PIV credentials if ($ssl_client_verify != SUCCESS) { return 403; } } Save and exit. Restart NGINX after modifying the configuration: # nginx -s reload
- RMF Control
- IA-5
- Severity
- M
- CCI
- CCI-002007
- Version
- NGNX-APP-001690
- Vuln IDs
-
- V-278401
- Rule IDs
-
- SV-278401r1171955_rule
Checks: C-82935r1171953_chk
If a keyval store is not used to store tokens, this is not applicable. Determine path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Determine if a keyval store is used and no timeout is specified: grep keyval <location of config> Example: keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h; If a timeout is not specified to an organization defined timeout value, this is a finding.
Fix: F-82840r1171954_fix
Determine path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Edit the config and set a timeout on any keyval storing credentials: keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h; Restart NGINX: nginx -s reload
- RMF Control
- SC-16
- Severity
- M
- CCI
- CCI-002455
- Version
- NGNX-APP-001840
- Vuln IDs
-
- V-278402
- Rule IDs
-
- SV-278402r1171958_rule
Checks: C-82936r1171956_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Verify the embedded security attributes are present as HTTP Headers: server { listen 443 ssl; server_name secure-api.example.com; location /data { proxy_pass http://backend_service; proxy_set_header X-Security-Classification "Confidential"; proxy_set_header X-Data-Origin "Internal-System"; proxy_set_header X-Access-Permissions "Read,Write"; } } If the "proxy_pass" variable is not set nor the "proxy_set_header" is not set for the required headers, this is a finding.
Fix: F-82841r1171957_fix
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Include the "proxy_pass" service as well as the "proxy_set_header" values as required: proxy_pass http://backend_service; proxy_set_header X-Security-Classification "Confidential"; proxy_set_header X-Data-Origin "Internal-System"; proxy_set_header X-Access-Permissions "Read,Write"; After saving the configuration, reload NGINX: # nginx -s reload
- RMF Control
- SC-23
- Severity
- M
- CCI
- CCI-002470
- Version
- NGNX-APP-001900
- Vuln IDs
-
- V-278403
- Rule IDs
-
- SV-278403r1171961_rule
Checks: C-82937r1171959_chk
Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Find any "ssl_certificate" ssl_client_certificate" directives and verify they are DOD approved. If the certificates are not DOD approved, this is a finding.
Fix: F-82842r1171960_fix
Replace any non-DOD issued certificates with DOD-issued certificates. After replacing the certificates, reload NGINX: # nginx -s reload
- RMF Control
- SC-5
- Severity
- M
- CCI
- CCI-002385
- Version
- NGNX-APP-001940
- Vuln IDs
-
- V-278404
- Rule IDs
-
- SV-278404r1171964_rule
Checks: C-82938r1171962_chk
Check for the "limit_req" or "limit_conn" directives in the NGINX configuration files: grep -R "limit_req\|limit_conn" /etc/nginx/ Determine if NGINX App Protect is enabled: grep -R "app_protect_enable on" /etc/nginx/ If the "lmit_req" or "limit connections" are not present, this is a finding.
Fix: F-82843r1171963_fix
Define a connection limiting zone. Open the NGINX configuration file in a text editor: sudo nano /etc/nginx/nginx.conf Establish a shared memory zone to track and limit connections from each client. Add this directive above the server block in the nginx.conf: limit_conn_zone $binary_remote_addr zone=conn_limit_zone:10m; $binary_remote_addr: Uses the client's IP address for limiting. zone=conn_limit_zone:10m: Allocates 10MB of memory for tracking connections. Adjust size based on your needs. Apply connection limiting in the server block. Now, apply connection limiting to the desired location block. Inside the location block, add the connection limiting directive: nginx Copy code server { location / { limit_req zone=one burst=20 nodelay; limit_conn conn_limit_zone 10; proxy_pass http://backend; } } limit_conn conn_limit_zone 10: Limits the client to 10 concurrent connections. Save and exit the file. Restart NGINX: # nginx -s reload
- RMF Control
- SC-8
- Severity
- M
- CCI
- CCI-002418
- Version
- NGNX-APP-001960
- Vuln IDs
-
- V-278405
- Rule IDs
-
- SV-278405r1171967_rule
Checks: C-82939r1171965_chk
Determine the path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Examine the SSL configuration settings: grep -R 'ssl_' /etc/nginx/nginx.conf Verify TLS versions: ssl_protocols TLSv1.2 TLSv1.3; Verify cipher suites: ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; ssl_prefer_server_ciphers on; Note: The cipher list can be more restrictive if defined by the organization. If non-FIPS ciphers or weak protocols (e.g., TLSv1.0/1.1, RC4, MD5, 3DES) are present, this is a finding.
Fix: F-82844r1171966_fix
Restrict TLS versions to FIPS-approved protocols: ssl_protocols TLSv1.2 TLSv1.3; Configure only FIPS compliant ciphers: ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; Do not allow clients to select the ciphers: ssl_prefer_server_ciphers on; Restart NGINX to apply changes: sudo nginx -t && sudo systemctl reload nginx
- RMF Control
- IA-5
- Severity
- M
- CCI
- CCI-000185
- Version
- NGNX-APP-002620
- Vuln IDs
-
- V-278406
- Rule IDs
-
- SV-278406r1171970_rule
Checks: C-82940r1171968_chk
If using CRL for certificate revocation, this requirement is Not Applicable. Determine the path to NGINX config file(s): # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. # cat <path to config> Check the http { blocks for the following example: http { server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client on; ssl_crl /etc/nginx/ssl/crl.pem; ssl_ocsp on; ssl_ocsp_responder http://ocsp.disa.mil; ssl_stapling on; ssl_stapling_verify on; ssl_stapling_file /etc/nginx/ssl/ocsp_cache.pem; ssl_stapling_responder_timeout 3s; # Timeout for OCSP responder queries ssl_stapling_responder_error_cache_time 300s; # Cache time for responder errors location / { proxy_pass http://backend; } } } Check for certificate path validation. If "ssl_verify_client on" is not in the configuration, this is a finding. Check if OCSP is enabled. If "ssl_ocsp on" is not in the configuration, this is a finding. Check if OCSP Stapling is configured. If "ssl_stapling on" or "ssl_stapling_verify on" is not in the configuration, this is a finding. If "ssl_stapling_file <file>" is not present in the configuration, this is a finding.
Fix: F-82845r1171969_fix
Edit the NGINX configuration file. Set "ssl_verify_client on", "ssl_ocsp on", ssl_stapling_verify on", and "ssl_stapling on" as shown in the example below. Create a local cache for OCSP responses: touch /etc/nginx/ssl/ocsp_cache.pem chmod 600 /etc/nginx/ssl/ocsp_cache.pem Set the "ssl_stapling_file" directive with the file created as shown in the example below. http { server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client on; ssl_crl /etc/nginx/ssl/crl.pem; ssl_ocsp on; ssl_ocsp_responder https://ocsp.disa.mil; ssl_stapling on; ssl_stapling_verify on; ssl_stapling_file /etc/nginx/ssl/ocsp_cache.pem; ssl_stapling_responder_timeout 3s; # Timeout for OCSP responder queries ssl_stapling_responder_error_cache_time 300s; # Cache time for responder errors location / { proxy_pass http://backend; } } }
- RMF Control
- SC-13
- Severity
- M
- CCI
- CCI-002450
- Version
- NGNX-APP-002660
- Vuln IDs
-
- V-278407
- Rule IDs
-
- SV-278407r1172754_rule
Checks: C-82941r1171971_chk
Verify NGINX is using OpenSSL with FIPS enabled. For version 1.x: # nginx -V nginx version: nginx/1.15.2 (nginx-plus-r16) built by gcc 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) built with OpenSSL 1.0.2k-fips 26 Jan 2017" If the response does not include "fips" in the OpenSSL version, this is a finding. For version 3.x: # openssl list -providers Providers: base name: OpenSSL Base Provider version: 3.2.2 status: active default name: OpenSSL Default Provider version: 3.2.2 status: active fips name: Red Hat Enterprise Linux 9 - OpenSSL FIPS Provider version: 3.2.2-622cc79c634cbbef status: active If the response does not list a FIPS provider with a status of "active", this is a finding.
Fix: F-82846r1172753_fix
FIPS must be enabled on the operating system. Follow the OS guidelines for installing FIPS mode. After installation, confirm that FIPS is enabled: # sudo sysctl –a | grep fips crypto.fips_enabled = 1 Install the FIPS-validated version of OpenSSL to the operating system.
- RMF Control
- Severity
- M
- CCI
- CCI-004066
- Version
- NGNX-APP-003040
- Vuln IDs
-
- V-278408
- Rule IDs
-
- SV-278408r1171976_rule
Checks: C-82942r1171974_chk
Determine path to NGINX config file(s): nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Identify the NGINX runtime user: grep -E '^\s*user\s+' /etc/nginx/nginx.conf Expected output (example): user nginx; Confirm the password is locked: passwd -S nginx Expected output (example): nginx LK 2025-05-21 -1 -1 -1 -1 (Password locked.) If the NGINX runtime user account is not locked for password changes, this is a finding.
Fix: F-82847r1171975_fix
Lock the password for the NGINX user (if not already locked): sudo passwd -l nginx
- RMF Control
- Severity
- M
- CCI
- CCI-004192
- Version
- NGNX-APP-003060
- Vuln IDs
-
- V-278409
- Rule IDs
-
- SV-278409r1171979_rule
Checks: C-82943r1171977_chk
If not using the NGINX API, this is Not Applicable. Determine path to NGINX config file: # nginx -qT | grep "# configuration" # configuration file /etc/nginx/nginx.conf: Note: The default NGINX configuration is "/etc/nginx/nginx.conf", though various files may also be included. Check that the nginx.conf file contains the API directive and a separate listen address: http { server { listen 192.168.0.1:80; location / { proxy_pass http://backend; } location /api { api write=on; } } } If the API is running on the same network as production traffic, this is a finding.
Fix: F-82848r1171978_fix
Configure the API directive to use a separate listen address from production traffic: http { server { listen 192.168.0.1:80; location / { proxy_pass http://backend; } } server { listen 10.0.0.1:80; location /api { api write=on; } } } After saving the updated config, restart NGINX: nginx -s reload.
- RMF Control
- Severity
- M
- CCI
- CCI-005156
- Version
- NGNX-APP-003220
- Vuln IDs
-
- V-278410
- Rule IDs
-
- SV-278410r1172694_rule
Checks: C-82944r1172693_chk
Check SSL/TLS certificate and private key file permissions: # ls -la /home/ubuntu/nginx.com.crt # ls -la /home/ubuntu/nginx.com.key Verify: - Certificate file permissions are 644 or more restrictive. - Private key file permissions are 600 or more restrictive. - Files are owned by nginx user or root. - Files are not world-readable or group-writable. If these permissions are not set, this is a finding. Verify certificate validity and strength: # openssl x509 -in /home/ubuntu/nginx.com.crt -text -noout Verify: - Certificate is not expired. - Uses RSA key length of 2048 bits minimum or ECDSA P-256 minimum. - Signature algorithm is SHA-256 or stronger (not SHA-1 or MD5). - Certificate chain is complete and valid. If these values are not met, this is a finding. Verify private key strength and protection: # openssl rsa -in /home/ubuntu/nginx.com.key -text -noout -check Verify: - Key length is 2048 bits minimum. - Key is not encrypted with weak algorithms. - Key passes integrity check. If these key values are not set, this is a finding.
Fix: F-82849r1171981_fix
Set proper file permissions for SSL certificate and private key: # chmod 644 /home/ubuntu/nginx.com.crt # chmod 600 /home/ubuntu/nginx.com.key # chown nginx:nginx /home/ubuntu/nginx.com.crt # chown nginx:nginx /home/ubuntu/nginx.com.key Move certificates to secure location: # mkdir -p /etc/nginx/ssl # mv /home/ubuntu/dev.sports.com.* /etc/nginx/ssl/ # chmod 700 /etc/nginx/ssl Update NGINX configuration to use secure certificate location: server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/nginx.com.crt; ssl_certificate_key /etc/nginx/ssl/nginx.com.key; ssl_session_cache shared:SSL:10m; ssl_dhparam /etc/nginx/ssl/dhparam.pem; } Generate strong DH parameters if not present: # openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 # chmod 644 /etc/nginx/ssl/dhparam.pem
- RMF Control
- Severity
- M
- CCI
- CCI-005167
- Version
- NGNX-APP-003330
- Vuln IDs
-
- V-278411
- Rule IDs
-
- SV-278411r1172756_rule
Checks: C-82945r1172755_chk
Verify NGINX Plus revokes or invalidates access tokens: Check support for token revocation enforcement: # grep -i "introspect\|revok\|blacklist\|logout" /etc/nginx/conf.d/*.conf Confirm token validation includes checking revocation status using: - OAuth2 introspection endpoint. - Token revocation list. - Allowlist or deny-list implementation. - Explicit session logout propagation. Inspect logout and session termination handling: # grep -i "session_timeout\|logout\|revoke" /etc/nginx/conf.d/*.conf Ensure sessions are invalidated upon: - User logout. - Token expiration. - Authentication source change (e.g., password reset). Token revocation events (manual or automatic) must be logged with user ID, reason, and timestamp. If NGINX Plus does not support access token revocation or fails to enforce revocation upon compromise or logout, this is a finding.
Fix: F-82850r1171984_fix
Configure NGINX Plus to enforce access token revocation mechanisms. Enable token revocation via introspection endpoint (OIDC/OAuth2): location = /token/introspect { internal; proxy_pass https://auth.example.mil/oauth2/introspect; proxy_set_header Content-Type "application/x-www-form-urlencoded"; proxy_pass_request_body on; proxy_set_body "token=$http_authorization"; } Fail-closed on invalid or revoked token: location /secure/ { auth_request /token/introspect; error_page 401 = @error_revoke; } location @error_revoke { return 401 "Access token revoked or invalid."; } Revoke tokens during logout flows: Redirect logout requests to IdP to invalidate tokens: location = /logout { return 302 https://auth.example.mil/logout?token=$http_authorization; } Log all token revocation events: log_format token_revocation '$remote_addr - $remote_user [$time_local] ' '"$request" status=$status ' 'token_id="$jwt_jti" revoked="true" '; access_log /var/log/nginx/token_revocation.log token_revocation;