Optimizing NGINX & PHP-FPM...location / {root /usr/share/nginx/html; index index.html index.htm;}...

81
Optimizing NGINX & PHP-FPM — From Beginner to Expert to Crazy International PHP Conference Fall 2019 - Munich

Transcript of Optimizing NGINX & PHP-FPM...location / {root /usr/share/nginx/html; index index.html index.htm;}...

Optimizing NGINX & PHP-FPM— From Beginner to Expert to Crazy

International PHP Conference Fall 2019 - Munich

Arne Blankerts

Co-Founder, The PHP Consulting Company

Basics

location / { root /usr/share/nginx/html; index index.html index.htm;}

location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;}

location / { root /usr/share/nginx/html; index index.html index.htm;}

location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/$fastcgi_script_name;}

location / { root /usr/share/nginx/html; index index.html index.htm;}

location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

location / { root /usr/share/nginx/html; index index.html index.htm;}

location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

location / { root /usr/share/nginx/html; index index.html index.htm;}

location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

location / { root /usr/share/nginx/html; index index.html index.htm;

try_files $uri $uri/ @php;}

location @php { fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

root /usr/share/nginx/html;

location / { try_files $uri $uri/ @php;}

location @php { fastcgi_pass unix:/var/run/php-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

Compress all the things!

root /usr/share/nginx/html;

location / { gzip_static on; try_files $uri $uri/ @php;}

location @php { gzip on; fastcgi_pass unix:/var/run/php-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

Content Security Policy

server { root /usr/share/nginx/html;

add_header Content-Security-Policy "default-src 'none'; img-src 'self'; \ script-src 'self'; style-src 'self'; font-src 'self'; base-uri 'self'; \ form-action 'self'; connect-src 'self'; frame-ancestors 'none'";}

server { root /usr/share/nginx/html;

include content-security-policy;}

https://csp-evaluator.withgoogle.com

https://content-security-policy.com

https://report-uri.com/home/generate

Additional Security Headers

server { root /usr/share/nginx/html;

include content-security-policy;

add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-Xss-Protection "1; mode=block" always;}

Force Https

server { listen 80 default_server; listen [::]:80 default_server;

return 301 https://$host$request_uri;}

Sane TLS

server { listen 443 ssl http2; listen [::]:443 ssl http2;

ssl_certificate /path/to/signed_cert_plus_intermediates; ssl_certificate_key /path/to/private_key; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off;

ssl_dhparam /path/to/dhparam.pem;

ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:...:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off;

add_header Strict-Transport-Security "max-age=63072000" always;

ssl_stapling on; ssl_stapling_verify on;

ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

resolver 127.0.0.1;}

Paranoid TLS

server { listen 443 ssl http2; listen [::]:443 ssl http2;

ssl_certificate /path/to/signed_cert_plus_intermediates; ssl_certificate_key /path/to/private_key; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off;

ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off;

add_header Strict-Transport-Security "max-age=63072000" always;

ssl_stapling on; ssl_stapling_verify on;

ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

resolver 127.0.0.1;}

https://observatory.mozilla.org

https://ssl-config.mozilla.org

https://securityheaders.com

More than one backend

location @php { fastcgi_pass unix:/var/run/php-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

upstream php-fpm { server unix:/var/run/php-fpm.sock;}

location @php { fastcgi_pass php-fpm; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

upstream php-fpm { server unix:/var/run/php-fpm.sock;

server 192.168.1.1:9000 weigth=5; server 192.168.1.2:1345 max_fails=3 fail_timeout=30s; server 192.168.1.3:9000 down;

server 192.168.1.4:9000 backup;}

location @php { fastcgi_pass php-fpm; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

Semi dynamic backends

upstream php-fpm { server unix:/var/run/php-fpm.sock;

include upstreams/*.conf;}

location @php { fastcgi_pass php-fpm; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

Keep in touch

upstream php-fpm { server unix:/var/run/php-fpm.sock; include upstreams/*.conf;

keepalive 10; }

location @php { fastcgi_pass php-fpm; fastcgi_keep_conn on; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;}

O�oad Logging

location @php { fastcgi_pass php-fpm; fastcgi_keep_conn on; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php;

mirror @logging;}

location @logging { fastcgi_pass logger-pool; fastcgi_param SCRIPT_FILENAME /var/www/php/log.php; include fastcgi_params; fastcgi_param X-Original-Url $request_uri;}

location @php { fastcgi_pass php-fpm; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php; include fastcgi_params;

mirror @logging; mirror_request_body off;}

location @logging { fastcgi_pass logger-pool; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/php/log.php; fastcgi_param X-Original-Url $request_uri;}

Who handles errors?

server { errorpage 404 /error/404.html;

location @php { fastcgi_pass php-fpm; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php; include fastcgi_params;

fastcgi_intercept_errors on; }

location /error/ { root /var/www/errors; }}

map $http_accept_language $lang { default en; ~de de; # ...}

server {

errorpage 404 /error/$lang/404.html;

location @php { fastcgi_pass php-fpm; fastcgi_param SCRIPT_FILENAME /var/www/php/index.php; include fastcgi_params;

fastcgi_intercept_errors on; }

location /error/ { root /var/www/errors; }}

More fun with maps

More fun with maps

Also known as API versioning

map $accept_header $backend { default ‘unix:/var/run/app_v1.sock’; ‘application/vnd.some-site.v2+json’ ‘unix:/var/run/app_v2.sock’; ‘application/vnd.some-site.v3+json’ ‘192.168.1.1:9000’;}

server { location @php { fastcgi_pass $backend; }}

map $accept_header $backend { default ‘unix:/var/run/app_v1.sock’;

include /var/www/versions/*.conf;}

server { location @php { fastcgi_pass $backend; }}

server {

location / { try_files $uri $uri/ @new; }

location @new {

# ...

fastcgi_intercept_errors on; recursive_error_pages on;

error_page 404 = @old;

}

location @old {

# ...

}}

Sane Downloads

class DownloadQuery {

// ...

public function execute(): Response { if (!$this->acl->isAccessGranted($this->file) { return new AccessDeniedResponse(); }

return new DownloadResponse($this->path . '/' . $this->file); }}

class DownloadQuery {

// ...

public function execute(): Response { if (!$this->acl->isAccessGranted($this->file) { return new AccessDeniedResponse(); }

return new DownloadResponse($this->path . '/' . $this->file); }}

class DownloadResponse implements Response {

// ...

public function send() { header('X-Accel-Redirect: ' . $this->file); } }

class DownloadQuery {

// ...

public function execute(): Response { if (!$this->acl->isAccessGranted($this->file) { return new AccessDeniedResponse(); }

return new DownloadResponse($this->path . '/' . $this->file); }}

class DownloadResponse implements Response {

// ...

public function send() { header('X-Accel-Redirect: ' . $this->file); } }

Sane large file uploads

server {

location /upload { limit_except POST { deny all; }

client_body_temp_path /var/www/data/uploads; client_body_in_file_only on; client_body_buffer_size 1024K; client_max_body_size 10G;

fastcgi_pass_request_headers on; fastcgi_pass_request_body off; fastcgi_ignore_client_abort on; fastcgi_param X-UPLOAD-FILE $request_body_file; }}

// ...

let file = formElement.querySelector('input[type=file]').files[0];let xhr = new XMLHttpRequest();

xhr.open('POST', '/upload', true);xhr.setRequestHeader('X-Original', file.name);xhr.send(file);

// ...

Tweaking FPM defaults

[www]pm = staticpm.max_children = 10pm.max_requests = 500

[www]pm = staticpm.max_children = 10pm.max_requests = 500

slowlog = /var/log/php-fpm/$pool.log.slowrequest_slowlog_timeout = 1s

Configure Opcache

opcache.max_accelerated_files=20000opcache.memory_consumption=512

opcache.max_accelerated_files=20000opcache.memory_consumption=512

opcache.validate_timestamps=0opcache.file_update_protection=0opcache.save_comments=0

opcache.max_accelerated_files=20000opcache.memory_consumption=512

opcache.validate_timestamps=0opcache.file_update_protection=0opcache.save_comments=0

opcache.preload = /path/to/preload.php

Make Sessions fast

Make Sessions faster

session.probability = 0

session.probability = 0

session.save_handler = redissession.save_path = unix://var/run/redis.sock?persistent=1

session.probability = 0

session.save_handler = redissession.save_path = unix://var/run/redis.sock?persistent=1

session.save_path = tcp://10.0.0.1:6379?persistent=1

Filter all the things?

location /img-low/ { alias /var/www/public/img/;

image_filter resize 150 100; error_page 415 = @empty;}

location @empty { empty_gif;}

location /feed/ { proxy_pass http://feed-server/atom;

xslt_stylesheet /var/www/xsl/atom.xsl;}

JavaScript!

js_include path/to/switch.js;js_set $current select_pool;

server {

// [...]

location @php { fastcgi_pass unix:/var/run/php-fpm/$current.sock; include fastcgi_params; fastcgi_param REQUEST_URI $document_uri; fastcgi_param SCRIPT_FILENAME /var/www/php/bootstrap.php; }

}

js_include path/to/switch.js;js_set $current select_pool;

server {

// [...]

location @php { fastcgi_pass unix:/var/run/php-fpm/$current.sock; include fastcgi_params; fastcgi_param REQUEST_URI $document_uri; fastcgi_param SCRIPT_FILENAME /var/www/php/bootstrap.php; }

}

function select_pool() { return 'www';}

Or Lua!

server {

location @php { access_by_lua_file switch.lua;

fastcgi_pass unix:/var/run/php-fpm/$current.sock; include fastcgi_params; fastcgi_param REQUEST_URI $document_uri; fastcgi_param SCRIPT_FILENAME $current/runtime/bootstrap.php; }

}

ngx.var.current = 'www';return

Single Sign On with Lua

local uuid = ngx.var.cookie_uuid;if not uuid then ngx.redirect('/login');end

local redis = require "resty.redis"local red = redis:new()red:connect("127.0.0.1", 6379)local res, err = red:get(uuid)if res == ngx.null then ngx.redirect('/login');end

ngx.header["uuid"] = nilngx.req.set_header("X-UUID-INFO", uuid);ngx.req.set_header("X-USER-INFO", res);return

Sticky session

local sid = ngx.var.cookie_PHPSESSID;local redis = require "resty.redis"local red = redis:new()

local keys = {'default'}if sid then keys[2] = sidend

red:set_timeout(100)local ok, err = red:connect("127.0.0.1", 6379)

local res, err = red:mget(unpack(keys))if (res[2] == ngx.null) then res[2] = nilend

if sid and not res[2] then red:set(sid, res[1])end

ngx.var.current = res[2] or res[1]return