-
-
Save virgilwashere/013a88e3dde2408808772e50988548c0 to your computer and use it in GitHub Desktop.
# /etc/nginx/conf.d/fastcgi-params.conf | |
# included by /etc/nginx/conf.d/fastcgi-php-nginx.conf | |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
fastcgi_param QUERY_STRING $query_string; | |
fastcgi_param REQUEST_METHOD $request_method; | |
fastcgi_param CONTENT_TYPE $content_type; | |
fastcgi_param CONTENT_LENGTH $content_length; | |
fastcgi_param SCRIPT_NAME $fastcgi_script_name; | |
fastcgi_param REQUEST_URI $request_uri; | |
fastcgi_param DOCUMENT_URI $document_uri; | |
fastcgi_param DOCUMENT_ROOT $document_root; | |
fastcgi_param SERVER_PROTOCOL $server_protocol; | |
fastcgi_param REQUEST_SCHEME $scheme; | |
fastcgi_param HTTPS $https if_not_empty; | |
fastcgi_param GATEWAY_INTERFACE CGI/1.1; | |
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; | |
fastcgi_param REMOTE_ADDR $remote_addr; | |
fastcgi_param REMOTE_PORT $remote_port; | |
fastcgi_param SERVER_ADDR $server_addr; | |
fastcgi_param SERVER_PORT $server_port; | |
fastcgi_param SERVER_NAME $server_name; | |
# PHP only, required if PHP was built with --enable-force-cgi-redirect | |
fastcgi_param REDIRECT_STATUS 200; |
# /etc/nginx/conf.d/fastcgi-php-nginx.conf | |
# regex to split $uri to $fastcgi_script_name and $fastcgi_path | |
fastcgi_split_path_info ^(.+?\.php)(/.*)$; | |
# Bypass the fact that try_files resets $fastcgi_path_info | |
# see: http://trac.nginx.org/nginx/ticket/321 | |
set $path_info $fastcgi_path_info; | |
fastcgi_param PATH_INFO $path_info; | |
# DONE: investigate which check is prefered. IfIsEvil. | |
# Check that the PHP script exists before passing it | |
try_files $fastcgi_script_name =404; | |
include /etc/nginx/conf.d/fastcgi-params.conf; | |
fastcgi_param SCRIPT_FILENAME $request_filename; | |
# Mitigate https://httpoxy.org/ vulnerabilities | |
fastcgi_param HTTP_PROXY ""; | |
fastcgi_index index.php; | |
fastcgi_pass unix:/var/run/php/php-fpm.sock; | |
# [Setting Nginx FastCGI response buffer sizes](http://bit.ly/nginx_fastcgi_buffers) | |
fastcgi_buffers 128 4k; # Modified number at default size. | |
# Do NOT change buffer_size. See Gist. # Current $fastcgi_buffers directive .. | |
# fastcgi_buffer_size 4k; # ... sets this to 512k | |
fastcgi_busy_buffers_size 8k; # default=2 x buffers | |
fastcgi_max_temp_file_size 1024m; # written in **$fastcgi_temp_file_write_size** chunks | |
fastcgi_temp_file_write_size 32k; # default=2 x buffers |
# /etc/nginx/sites-available/mauticdemo.nginx.conf | |
# nginx config for (Mautic) | |
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ | |
# https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ | |
server { | |
listen 443 ssl http2; | |
server_name mauticdemo.co; | |
root /var/www/mauticdemo/mautic; | |
ssl_certificate /etc/certs/mauticdemo.co/fullchain.cer; # managed by Certbot | |
ssl_certificate_key /etc/certs/mauticdemo.co/mauticdemo.co.key; # managed by Certbot | |
# include /etc/nginx/conf.d/options-ssl-nginx.conf; #in http{..} | |
# if ($scheme != "https") { | |
# return 301 https://$server_name$request_uri; | |
# } # managed by Certbot | |
add_header Strict-Transport-Security "max-age=31536000"; | |
add_header Access-Control-Allow-Headers 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since'; | |
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; | |
# add_header rt-Fastcgi-Cache $upstream_cache_status; | |
access_log /var/log/nginx/mauticdemo_access.log; | |
error_log /var/log/nginx/mauticdemo_error.log error; | |
# include /etc/nginx/conf.d/options-gzip-nginx.conf; # in http {..} | |
# vhost optimisation Settings | |
client_max_body_size 20M; | |
# index in .. http{} | |
error_page 404 /index.php; | |
# redirect index.php to webroot | |
# rewrite ^/index.php/(.*) /$1 permanent; | |
return 301 https://$server_name$request_uri; | |
location / { | |
# First attempt to serve request as file, | |
# then fall back to index.html | |
try_files $uri /index.php$is_args$args; | |
} | |
# FastCGI gets enabled for processing php files here. | |
location ~ [^/]\.php(/|$) { | |
include /etc/nginx/conf.d/fastcgi-php-nginx.conf | |
# Not required permanently. Output just FastCGI requests to its own Nginx log file. | |
# access_log /var/log/nginx/phpfpm-access.log; | |
} | |
################################ | |
## START OF MAUTIC SETTINGS ## | |
# redirect some entire folders | |
rewrite ^/(vendor|translations|build)/.* /index.php break; | |
# Deny everything else in /app folder except folder bundles/Assets | |
location ~ /app/bundles/.*/Assets/ { | |
allow all; | |
access_log off; | |
} | |
location ~ /app/ { deny all; } | |
# Deny everything else in /addons or /plugins folder except Assets folder | |
location ~ /(addons|plugins)/.*/Assets/ { | |
allow all; | |
access_log off; | |
} | |
# Add ^ to anchor regex | |
location ~ ^/(addons|plugins)/ { deny all; } | |
# Deny all .php files in themes folder | |
location ~* ^/themes/(.*)\.php { | |
deny all; | |
} | |
# Don't log favicon | |
location = /favicon.ico { | |
log_not_found off; | |
access_log off; | |
} | |
# Don't log robots | |
location = /robots.txt { | |
access_log off; | |
log_not_found off; | |
} | |
# Deny yaml, twig, markdown, ini file access | |
location ~* /(.*)\.(?:markdown|md|twig|yaml|yml|ht|htaccess|ini)$ { | |
deny all; | |
access_log off; | |
log_not_found off; | |
} | |
# Deny all attempts to access hidden files/folders such as .htaccess, .htpasswd, .DS_Store (Mac), etc... | |
location ~ /\. { | |
deny all; | |
access_log off; | |
log_not_found off; | |
} | |
# Deny all grunt, composer files | |
location ~* (Gruntfile|package|composer)\.(js|json|jsonc)$ { | |
deny all; | |
access_log off; | |
log_not_found off; | |
} | |
# Deny access to any files with a \.php extension in the uploads directory | |
location ~* /(?:uploads|files)/.*\.php$ { | |
deny all; | |
} | |
# Solve email tracking pixel not found | |
location ~ email/(.*).gif { | |
try_files $uri /index.php?$args; | |
} | |
location ~ mtracking.gif { | |
add_header 'Access-Control-Allow-Origin' *; | |
try_files $uri /index.php?$args; | |
} | |
# Solve js loading error: 404 | |
location ~ mtc.js { | |
add_header 'Access-Control-Allow-Origin' *; | |
try_files $uri /index.php?$args; | |
} | |
location ~* ^/media/js/$ { | |
add_header 'Cache-Control public'; | |
expires 7d; | |
} #this should fall through | |
location ~ (.*).js { | |
try_files $uri /index.php?$args; | |
} | |
# Set cache expiry time | |
location ~* \.(jpg|jpeg|png|ico|css)$ { | |
expires 365d; | |
} | |
location ~* \.(pdf)$ { | |
expires 30d; | |
} | |
## END OF MAUTIC SETTINGS ## | |
############################ | |
} | |
server { | |
listen 80; | |
server_name mauticdemo.co; | |
access_log off; | |
# Redirect any http requests to https | |
location / { | |
return 301 https://$server_name$request_uri; | |
} | |
} |
user www-data; | |
pid /run/nginx.pid; | |
error_log /var/log/nginx/main_error.log; | |
worker_processes auto; | |
events { | |
worker_connections 768; | |
# multi_accept on; | |
} | |
http { | |
## | |
# Basic Settings | |
## | |
server_tokens off; | |
sendfile on; | |
tcp_nopush on; | |
tcp_nodelay on; | |
keepalive_timeout 15; | |
types_hash_max_size 2048; | |
# server_names_hash_bucket_size 64; | |
server_name_in_redirect off; | |
underscores_in_headers on; | |
include mime.types; | |
default_type application/octet-stream; | |
## | |
# SSL Settings - Drops SSLv3, ref: POODLE | |
## | |
include /etc/nginx/conf.d/options-ssl-nginx.conf; | |
## | |
# Logging Settings | |
## | |
access_log /var/log/nginx/http_access.log; | |
error_log /var/log/nginx/http_error.log error; | |
## | |
# Gzip Settings | |
## | |
include /etc/nginx/conf.d/options-gzip-nginx.conf; | |
## | |
# Virtual Host Configs | |
## | |
include /etc/nginx/conf.d/*.conf; | |
include /etc/nginx/sites-enabled/*; | |
# Optimisation Settings | |
client_body_buffer_size 10K; | |
client_header_buffer_size 1k; | |
client_max_body_size 8m; | |
large_client_header_buffers 2 1k; | |
client_body_timeout 12; | |
client_header_timeout 12; | |
send_timeout 10; | |
} |
- Introduction
- Buffers? Meh
- Determine actual FastCGI response sizes
- Setting the buffer size
- Verifying our results
- Updated config snippet
Permanent link to this document: Setting Nginx FastCGI response buffer sizes
Shoutout to Peter Mescalchin (@magnetikonline) for the original Gist.
NB configs in this Markdown may not be current. The included files are up-to-date.
By default when Nginx starts receiving a response from a FastCGI backend (such as PHP-FPM) it will buffer the response in memory before delivering it to the client. Any response larger than the set buffer size is saved to a temporary file on disk.
This process is outlined at the Nginx ngx_http_fastcgi_module manual page.
Since disk is slow and memory is fast the aim is to get as many FastCGI responses passing only through memory. On the flip side we don't want to set an excessively large buffer as they are created and sized on a per request basis - it's not shared.
The related Nginx options are:
-
fastcgi_buffering
first appeared in Nginx 1.5.6 (1.6.0 stable) and can be used to turn buffering completely on/off. It's on by default. -
fastcgi_buffer_size
is a special buffer space used to hold the first chunk of the FastCGI response, which is going to be the HTTP response headers.You shouldn't need to adjust this from the default - even if Nginx defaults to the smallest page size of 4KB (your platform will determine if
4/8k
buffer) it should fit your typical HTTP header.One exception is frameworks that push large amounts of cookie data via the
Set-Cookie
HTTP header during their user verification/login phase - blowing out the buffer and resulting in a HTTP 500 error. In those instances you will need to increase this buffer to8k/16k/32k
to fully accommodate your largest upstream HTTP header being pushed. -
fastcgi_buffers
controls the number and memory size of buffer segments used for the payload of the FastCGI response.
Most, if not all of our tweaking will be based on the fastcgi_buffers
directive for the remainder of this guide.
By processing our Nginx access logs we can determine both maximum and average response sizes. The basis of this awk
recipe was found here.
awk '($10~/200/) {i++;sum+=$11;max=$11>max?$11:max;} END {printf("Maximum=%d\nAverage=%d\n",max,i?sum/i:0);}' access.log
Maximum: 575458
Average: 10215
Note: this recipe are going to report on all access requests returning an HTTP 200
code, you might want to filter FastCGI requests into a separate Nginx access logfile for reporting. For example, for PHP-FPM
:
location ~ \.php$ {
include /etc/nginx/snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
# Not required permanently. Output FastCGI requests to it's own Nginx log file
access_log /var/log/nginx/phpfpmonly-access.log;
}
Process the access requests from the newly configured php-fpm-access.log
:
awk '($10~/200/) {i++;sum+=$11;max=$11>max?$11:max;} END {printf("Maximum=%d\nAverage=%d\n",max,i?sum/i:0);}' phpfpm-access.log
Maximum: 569990
Average: 11820
The default memory pagesize (in bytes) for an operating system can be determined by the following command:
$ getconf PAGESIZE
4096
With these values in hand we are now much better equipped to set fastcgi_buffers
.
The fastcgi_buffers
setting takes two values, buffer segment count and memory size, by default it will be:
fastcgi_buffers 8 4k|8k;
So a total of 8 buffer segments at either 4k/8k
, which is determined by the platform memory page size.
For Debian/Ubuntu Linux that turns out to be 4096
bytes (4K) - so a default total buffer size of 32KB.
Based on the maximum/average response sizes determined above we can now raise/lower these values to suit. Typically you would keep buffer size at the default value (memory pagesize) and adjust the buffer segment count to a value that keeps the bulk of responses handled by FastCGI loaded in buffer RAM.
If your response size averages are on the higher side, consider lowering the buffer segment count and increasing the memory size in pagesize multiples (8k/16k/32k
).
We can see how often FastCGI responses are being saved to disk by grepping our Nginx error log(s):
grep --extended-regexp "\[warn\].+buffered" error.log
# or the older gzipped files
zgrep --extended-regexp "\[warn\].+buffered" error.log.2.gz
# will return lines like:
YYYY/MM/DD HH:MM:SS [warn] 1234#0: *123456 an upstream response is buffered to a temporary file...
Remember it's not necessarily a bad situation to have some larger responses buffered to disk. Aim for a balance where only a small portion of your largest responses are handled in this way.
Warning: Do NOT try to stuff all your fastcgi_buffers
responses into memory
WARNING: The practice of ramping up
fastcgi_buffers
to an excessive number and/or size value in an attempt to fit all FastCGI responses purely in RAM is something Peter and Virgil would strongly recommend against.
Unless your Nginx server is only receiving a few concurrent requests at any one time, you risk exhausting available system memory.
# Setting Nginx FastCGI response buffer sizes
# http://bit.ly/nginx_fastcgi_buffers
location ~ \.php$ {
include /etc/nginx/snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_buffers 128 4k; # Modified count at default size.
# Do NOT change buffer_size. See Gist. # Current $fastcgi_buffers directive ..
# fastcgi_buffer_size 4k; # ... sets this to 512k
fastcgi_busy_buffers_size 8k; # default=2 x buffers
fastcgi_max_temp_file_size 1024m; # written in **$fastcgi_temp_file_write_size** chunks
fastcgi_temp_file_write_size 32k; # default=2 x buffers
# TODO: New directives to research
# fastcgi_read_timeout 600;
# fastcgi_send_timeout 600;
# Not required permanently. Output just FastCGI requests to it's own Nginx log file.
# access_log /var/log/nginx/phpfpm-access.log;
}
# /etc/nginx/conf.d/options-gzip-nginx.conf | |
# included by /etc/nginx/nginx.conf | |
## GZIP BEGIN | |
# include /etc/nginx/conf.d/options-gzip-nginx.conf; | |
gzip on; | |
gzip_disable "msie6"; | |
gzip_min_length 256; | |
gzip_http_version 1.1; | |
gzip_vary on; | |
gzip_proxied expired no-cache no-store private auth; #any; | |
gzip_comp_level 2; #6 | |
gzip_buffers 16 8k; | |
gzip_types | |
font/truetype | |
font/opentype | |
font/woff2 | |
text/plain | |
text/css | |
text/js | |
text/xml | |
text/javascript | |
application/javascript | |
application/x-javascript | |
application/json | |
application/xml | |
application/rss+xml | |
image/svg+xml; | |
## GZIP END |
# /etc/nginx/conf.d/options-ssl-nginx.conf | |
# included by /etc/nginx/nginx.conf | |
ssl_session_cache shared:nginx_SSL:1m; | |
ssl_session_timeout 1440m; | |
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; | |
ssl_prefer_server_ciphers on; | |
ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; | |
# TODO: ssl-intemediate |
If you are not getting a useful result from awk
, change the columns back to $9
and $10
awk '($9~/200/) {i++;sum+=$10;max=$10>max?$10:max;} END {printf("Maximum=%d\nAverage=%d\n",max,i?sum/i:0);}' access.log
TODO
- update SSL config -
ssl_ciphers
/ssl_protocols
- confirm fastcgi 404 config method
- add FPM
php.ini
- add FPM pool
$pool.conf
@virgilwashere no updates on this?
Changelog
Access-Control-Allow-Headers
in mauticdemo-nginx.confPHP-FPM
config stanza.zgrep
examplecat error.log |
from the Verifying section commandsawk
columns from$9 $10
to$10 $11
as ourlog_format
prepends a$host:
columnnginx.conf
config file example with @virgilwashere real world example