Sicherheit · 12 min read · Jan 22, 2026

PHP-FPM/Nginx-Sicherheit in Shared Hosting-Umgebungen (Debian/Ubuntu)

PHP-FPM/Nginx-Sicherheit in Shared Hosting-Umgebungen (Debian/Ubuntu)

Version 1.0
Autor: Falko Timme
Folge mir auf Twitter

Wenn Sie nginx und PHP-FPM für Shared Hosting-Umgebungen verwenden möchten, sollten Sie sich Gedanken über die Sicherheit machen. In Apache/PHP-Umgebungen können Sie suExec und/oder suPHP verwenden, um PHP unter einzelnen Benutzerkonten anstelle eines Systembenutzers wie www-data auszuführen. Für PHP-FPM gibt es so etwas nicht, aber glücklicherweise erlaubt uns PHP-FPM, einen “Pool” für jede Website einzurichten, der PHP-Skripte als den in diesem Pool definierten Benutzer/Gruppe ausführt. Dies gibt Ihnen alle Vorteile von suPHP, und zusätzlich haben Sie keine FTP- oder SCP-Übertragungsprobleme, da PHP-Skripte nicht von einem bestimmten Benutzer/einer bestimmten Gruppe besessen werden müssen, um als der im Pool definierte Benutzer/Gruppe ausgeführt zu werden.

Ich gebe keine Garantie, dass dies für Sie funktioniert!

1 Vorbemerkung

Ich verwende hier einen vhost namens www.example.com / example.com mit dem Dokumentenstamm /var/www/www.example.com/web.

Sie sollten eine funktionierende LEMP-Installation haben, wie in diesen Tutorials gezeigt:

  • Nginx mit PHP5 und MySQL-Unterstützung auf Debian Squeeze installieren
  • Nginx mit PHP5 (und PHP-FPM) und MySQL-Unterstützung auf Ubuntu 11.04 installieren

Eine Anmerkung für Ubuntu-Nutzer:

Da wir alle Schritte aus diesem Tutorial mit Root-Rechten ausführen müssen, können wir entweder allen Befehlen in diesem Tutorial den String sudo voranstellen oder wir werden jetzt Root, indem wir eintippen

sudo su

2 Was wir bisher haben

Auf Debian/Ubuntu ist das Pool-Verzeichnis von PHP-FPM /etc/php5/fpm/pool.d/ - hier werden neue Pools erstellt. Die von PHP-FPM verwendete php.ini ist /etc/php5/fpm/php.ini. Es gibt bereits einen Pool, www.conf - schauen wir uns diesen an:

vi /etc/php5/fpm/pool.d/www.conf

| ; Starte einen neuen Pool namens 'www'. ; die Variable $pool kann in jeder Direktive verwendet werden und wird durch den ; Poolnamen ('www' hier) ersetzt [www] ; Per Pool-Präfix ; Es gilt nur für die folgenden Direktiven: ; - 'slowlog' ; - 'listen' (unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; Wenn nicht gesetzt, gilt stattdessen das globale Präfix (oder /usr). ; Hinweis: Diese Direktive kann auch relativ zum globalen Präfix sein. ; Standardwert: keine ;prefix = /path/to/pools/$pool ; Die Adresse, an der FastCGI-Anfragen akzeptiert werden. ; Gültige Syntaxen sind: ; 'ip.add.re.ss:port' - um auf einem TCP-Socket zu einer bestimmten Adresse auf ; einem bestimmten Port zu hören; ; 'port' - um auf einem TCP-Socket zu allen Adressen auf einem ; bestimmten Port zu hören; ; '/path/to/unix/socket' - um auf einem Unix-Socket zu hören. ; Hinweis: Dieser Wert ist obligatorisch. listen = 127.0.0.1:9000 ; Setze listen(2) backlog. Ein Wert von '-1' bedeutet unbegrenzt. ; Standardwert: 128 (-1 auf FreeBSD und OpenBSD) ;listen.backlog = -1 ; Liste der ipv4-Adressen von FastCGI-Clients, die sich verbinden dürfen. ; Entspricht der FCGI_WEB_SERVER_ADDRS-Umgebungsvariable in der ursprünglichen ; PHP FCGI (5.2.2+). Macht nur mit einem TCP-Listening-Socket Sinn. Jede Adresse ; muss durch ein Komma getrennt werden. Wenn dieser Wert leer gelassen wird, werden ; Verbindungen von jeder IP-Adresse akzeptiert. ; Standardwert: beliebig ;listen.allowed_clients = 127.0.0.1 ; Setze Berechtigungen für den Unix-Socket, falls einer verwendet wird. In Linux müssen ; Lese-/Schreibberechtigungen gesetzt werden, um Verbindungen von einem Webserver zu ;zulassen. Viele BSD-abgeleitete Systeme erlauben Verbindungen unabhängig von den ; Berechtigungen. ; Standardwerte: Benutzer und Gruppe werden als der laufende Benutzer gesetzt ; Modus wird auf 0666 gesetzt ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0666 ; Unix-Benutzer/-Gruppe der Prozesse ; Hinweis: Der Benutzer ist obligatorisch. Wenn die Gruppe nicht gesetzt ist, wird die ; Gruppe des Standardbenutzers verwendet. user = www-data group = www-data ; Wählen Sie, wie der Prozessmanager die Anzahl der Kindprozesse steuern wird. ; Mögliche Werte: ; static - eine feste Anzahl (pm.max_children) von Kindprozessen; ; dynamic - die Anzahl der Kindprozesse wird dynamisch basierend auf den ; folgenden Direktiven festgelegt: ; pm.max_children - die maximale Anzahl von Kindern, die gleichzeitig ; leben können. ; pm.start_servers - die Anzahl der Kinder, die beim Start erstellt werden. ; pm.min_spare_servers - die minimale Anzahl von Kindern im 'idle' ; Zustand (wartend auf Verarbeitung). Wenn die Anzahl ; der 'idle'-Prozesse kleiner ist als diese ; Zahl, werden einige Kinder erstellt. ; pm.max_spare_servers - die maximale Anzahl von Kindern im 'idle' ; Zustand (wartend auf Verarbeitung). Wenn die Anzahl ; der 'idle'-Prozesse größer ist als diese ; Zahl, werden einige Kinder beendet. ; Hinweis: Dieser Wert ist obligatorisch. pm = dynamic ; Die Anzahl der Kindprozesse, die erstellt werden sollen, wenn pm auf 'static' gesetzt ist, ; und die maximale Anzahl von Kindprozessen, die erstellt werden sollen, wenn pm auf 'dynamic' gesetzt ist. ; Dieser Wert setzt das Limit für die Anzahl gleichzeitiger Anfragen, die bedient werden. ; Entspricht der ApacheMaxClients-Direktive mit mpm_prefork. ; Entspricht der PHP_FCGI_CHILDREN-Umgebungsvariable in der ursprünglichen PHP ; CGI. ; Hinweis: Wird verwendet, wenn pm auf 'static' oder 'dynamic' gesetzt ist ; Hinweis: Dieser Wert ist obligatorisch. pm.max_children = 50 ; Die Anzahl der Kindprozesse, die beim Start erstellt werden. ; Hinweis: Wird nur verwendet, wenn pm auf 'dynamic' gesetzt ist ; Standardwert: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 ;pm.start_servers = 20 ; Die gewünschte Mindestanzahl an inaktiven Serverprozessen. ; Hinweis: Wird nur verwendet, wenn pm auf 'dynamic' gesetzt ist ; Hinweis: Obligatorisch, wenn pm auf 'dynamic' gesetzt ist pm.min_spare_servers = 5 ; Die gewünschte maximale Anzahl an inaktiven Serverprozessen. ; Hinweis: Wird nur verwendet, wenn pm auf 'dynamic' gesetzt ist ; Hinweis: Obligatorisch, wenn pm auf 'dynamic' gesetzt ist pm.max_spare_servers = 35 ; Die Anzahl der Anfragen, die jeder Kindprozess ausführen sollte, bevor er neu gestartet wird. ; Dies kann nützlich sein, um mit Speicherlecks in 3rd-Party-Bibliotheken umzugehen. Für ; endlose Anfrageverarbeitung geben Sie '0' an. Entspricht PHP_FCGI_MAX_REQUESTS. ; Standardwert: 0 ;pm.max_requests = 500 ; Die URI, um die FPM-Statusseite anzuzeigen. Wenn dieser Wert nicht gesetzt ist, wird keine URI ; als Statusseite erkannt. Standardmäßig zeigt die Statusseite die folgenden ; Informationen an: ; akzeptierte Verbindungen - die Anzahl der Anfragen, die vom Pool akzeptiert wurden; ; Pool - der Name des Pools; ; Prozessmanager - statisch oder dynamisch; ; inaktive Prozesse - die Anzahl der inaktiven Prozesse; ; aktive Prozesse - die Anzahl der aktiven Prozesse; ; insgesamt Prozesse - die Anzahl der inaktiven + aktiven Prozesse. ; maximale Kinder erreicht - Anzahl der Male, die die Prozessgrenze erreicht wurde, ; wenn pm versucht, mehr Kinder zu starten (funktioniert nur für ; pm 'dynamisch') ; Die Werte von 'inaktive Prozesse', 'aktive Prozesse' und 'insgesamt Prozesse' werden ; jede Sekunde aktualisiert. Der Wert von 'akzeptierte Verbindungen' wird in Echtzeit aktualisiert. ; Beispielausgabe: ; akzeptierte Verbindungen: 12073 ; Pool: www ; Prozessmanager: statisch ; inaktive Prozesse: 35 ; aktive Prozesse: 65 ; insgesamt Prozesse: 100 ; maximale Kinder erreicht: 1 ; Standardmäßig wird die Ausgabe der Statusseite als text/plain formatiert. Das Übergeben von ; entweder 'html' oder 'json' als Abfragezeichenfolge gibt die entsprechende Ausgabe ; Syntax zurück. Beispiel: ; http://www.foo.bar/status ; http://www.foo.bar/status?json ; http://www.foo.bar/status?html ; Hinweis: Der Wert muss mit einem führenden Schrägstrich (/) beginnen. Der Wert kann ; alles sein, aber es ist möglicherweise keine gute Idee, die .php-Erweiterung zu verwenden, oder es ; könnte mit einer echten PHP-Datei in Konflikt geraten. ; Standardwert: nicht gesetzt ;pm.status_path = /status ; Die Ping-URI, um die Überwachungsseite von FPM aufzurufen. Wenn dieser Wert nicht gesetzt ist, wird keine ; URI als Ping-Seite erkannt. Dies könnte verwendet werden, um von außen zu testen, ; dass FPM aktiv und reagiert, oder um ; - ein Diagramm der FPM-Verfügbarkeit zu erstellen (rrd oder ähnliches); ; - einen Server aus einer Gruppe zu entfernen, wenn er nicht reagiert (Lastenausgleich); ; - Warnungen für das Betriebsteam auszulösen (24/7). ; Hinweis: Der Wert muss mit einem führenden Schrägstrich (/) beginnen. Der Wert kann ; alles sein, aber es ist möglicherweise keine gute Idee, die .php-Erweiterung zu verwenden, oder es ; könnte mit einer echten PHP-Datei in Konflikt geraten. ; Standardwert: nicht gesetzt ;ping.path = /ping ; Diese Direktive kann verwendet werden, um die Antwort auf eine Ping-Anfrage anzupassen. Die ; Antwort wird als text/plain mit einem 200-Antwortcode formatiert. ; Standardwert: pong ;ping.response = pong ; Der Timeout für die Bearbeitung einer einzelnen Anfrage, nach dem der Arbeitsprozess ; beendet wird. Diese Option sollte verwendet werden, wenn die 'max_execution_time'-INI-Option ; aus irgendeinem Grund die Skriptausführung nicht stoppt. Ein Wert von '0' bedeutet 'aus'. ; Verfügbare Einheiten: s(econds)(Standard), m(inuten), h(ours) oder d(ays) ; Standardwert: 0 ;request_terminate_timeout = 0 ; Der Timeout für die Bearbeitung einer einzelnen Anfrage, nach dem ein PHP-Backtrace in die ; 'slowlog'-Datei ausgegeben wird. Ein Wert von '0s' bedeutet 'aus'. ; Verfügbare Einheiten: s(econds)(Standard), m(inuten), h(ours) oder d(ays) ; Standardwert: 0 ;request_slowlog_timeout = 0 ; Die Protokolldatei für langsame Anfragen ; Standardwert: nicht gesetzt ; Hinweis: slowlog ist obligatorisch, wenn request_slowlog_timeout gesetzt ist ;slowlog = log/$pool.log.slow ; Setze open file descriptor rlimit. ; Standardwert: systemdefinierter Wert ;rlimit_files = 1024 ; Setze max core size rlimit. ; Mögliche Werte: 'unbegrenzt' oder eine Ganzzahl größer oder gleich 0 ; Standardwert: systemdefinierter Wert ;rlimit_core = 0 ; Chroot in dieses Verzeichnis beim Start. Dieser Wert muss als ; absoluter Pfad definiert werden. Wenn dieser Wert nicht gesetzt ist, wird chroot nicht verwendet. ; Hinweis: Sie können mit '$prefix' voranstellen, um in das Pool-Präfix oder eines ; seiner Unterverzeichnisse zu chrooten. Wenn das Pool-Präfix nicht gesetzt ist, wird das globale ; Präfix stattdessen verwendet. ; Hinweis: Chrooting ist ein großartiges Sicherheitsmerkmal und sollte immer verwendet werden, ; wenn möglich. Alle PHP-Pfade werden relativ zum chroot sein ; (error_log, sessions.save_path, ...). ; Standardwert: nicht gesetzt ;chroot = ; Chdir in dieses Verzeichnis beim Start. ; Hinweis: relativer Pfad kann verwendet werden. ; Standardwert: aktuelles Verzeichnis oder / bei chroot chdir = / ; Leite stdout und stderr der Worker in das Hauptfehlerprotokoll um. Wenn nicht gesetzt, werden stdout und ; stderr gemäß den FastCGI-Spezifikationen nach /dev/null umgeleitet. ; Hinweis: In hochbelasteten Umgebungen kann dies zu Verzögerungen bei der Seitenverarbeitung ; (mehrere ms) führen. ; Standardwert: nein ;catch_workers_output = yes ; Übergebe Umgebungsvariablen wie LD_LIBRARY_PATH. Alle $VARIABLEs werden aus ; der aktuellen Umgebung übernommen. ; Standardwert: saubere Umgebung ;env[HOSTNAME] = $HOSTNAME ;env[PATH] = /usr/local/bin:/usr/bin:/bin ;env[TMP] = /tmp ;env[TMPDIR] = /tmp ;env[TEMP] = /tmp ; Zusätzliche php.ini-Definitionen, die spezifisch für diesen Pool von Workern sind. Diese Einstellungen ; überschreiben die zuvor in der php.ini definierten Werte. Die Direktiven sind die ; gleichen wie die PHP SAPI: ; php_value/php_flag - Sie können klassische ini-Definitionen festlegen, die ; von PHP-Aufruf 'ini_set' überschrieben werden können. ; php_admin_value/php_admin_flag - diese Direktiven werden nicht von ; PHP-Aufruf 'ini_set' überschrieben ; Für php_*flag sind gültige Werte an, aus, 1, 0, true, false, ja oder nein. ; Das Definieren von 'extension' lädt die entsprechende gemeinsame Erweiterung aus ; extension_dir. Das Definieren von 'disable_functions' oder 'disable_classes' wird nicht ; die zuvor definierten php.ini-Werte überschreiben, sondern den neuen Wert ; stattdessen anhängen. ; Hinweis: Pfad-INI-Optionen können relativ sein und werden mit dem Präfix ; (Pool, global oder /usr) erweitert ; Standardwert: standardmäßig ist nichts definiert, außer den Werten in php.ini und ; die beim Start mit dem -d-Argument angegeben sind ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected] ;php_flag[display_errors] = aus ;php_admin_value[error_log] = /var/log/fpm-php.www.log ;php_admin_flag[log_errors] = an ;php_admin_value[memory_limit] = 32M |

Wie Sie sehen, hört dieser Pool auf Port 9000 auf localhost (127.0.0.1) und wird als Benutzer und Gruppe www-data ausgeführt.

Schauen wir uns die PHP-Konfiguration in Ihrem vhost an:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Der wichtige Teil ist die Zeile fastcgi_pass 127.0.0.1:9000; - dies lässt nginx PHP-Anfragen an den PHP-FPM-Prozess weiterleiten, der auf Port 9000 auf localhost (127.0.0.1) hört - wie Sie sich erinnern, ist dies unser Pool, der in /etc/php5/fpm/pool.d/www.conf definiert ist, was bedeutet, dass PHP-Skripte als Benutzer und Gruppe www-data ausgeführt werden.

3 Definition eines individuellen Pools für jede Website

Meine example.com-Website gehört dem Benutzer web1 und der Gruppe client0, also möchte ich, dass meine PHP-Skripte als dieser Benutzer und diese Gruppe ausgeführt werden. Daher definiere ich einen neuen Pool /etc/php5/fpm/pool.d/example.com.conf:

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Wie Sie sehen, lasse ich diesen Pool auf Port 9001 anstelle von 9000 hören, und ich definiere den Benutzer als web1 und die Gruppe als client0. Sie können so viele Pools definieren, wie Sie möchten, aber stellen Sie sicher, dass Sie für jeden Pool einen unbenutzten Port verwenden (9002, 9003 usw.).

Laden Sie PHP-FPM neu:

/etc/init.d/php5-fpm reload

Jetzt ändern wir unsere vhost-Konfiguration, um den neuen Pool zu nutzen. Alles, was Sie ändern müssen, ist der Port in der fastcgi_pass-Zeile:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9001; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Laden Sie nginx danach neu:

/etc/init.d/nginx reload

Das war’s! PHP-Skripte werden jetzt als Benutzer web1 und Gruppe client0 ausgeführt.

Sie können PHP noch sicherer machen, indem Sie die PHP-Einstellungen individuell für jeden vhost ändern. Schauen Sie sich den unteren Teil von /etc/php5/fpm/pool.d/www.conf an, dort finden Sie einige Beispiele, wie Sie dies erreichen können.

Zum Beispiel könnten Sie open_basedir oder disable_functions im Pool /etc/php5/fpm/pool.d/example.com.conf festlegen.

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / php_admin_value[open_basedir] = /var/www/www.example.com:/usr/share/php5:/tmp:/usr/share/phpmyadmin:/etc/phpmyadmin:/var/lib/phpmyadmin php_admin_value[disable_functions] = dl,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source |

Laden Sie PHP-FPM neu:

/etc/init.d/php5-fpm reload

3.1 Verwendung von Sockets anstelle von TCP-Verbindungen

Bis jetzt haben wir TCP-Verbindungen für unseren PHP-FPM-Pool verwendet (127.0.0.1:9000, 127.0.0.1:9001 usw.). Dies verursacht einen gewissen Overhead. Glücklicherweise können wir Unix-Sockets anstelle von TCP-Verbindungen für unsere Pools verwenden und diesen Overhead loswerden. Daher sind Unix-Sockets leistungsfähiger als TCP-Verbindungen.

Ich möchte, dass Sockets im Verzeichnis /var/run/php5-fpm erstellt werden, daher müssen wir dieses Verzeichnis zuerst erstellen:

mkdir /var/run/php5-fpm

Um einen Unix-Socket zu verwenden, ändern wir einfach die listen-Zeile in unserer Pool-Definition, kommentieren die listen.allowed_clients-Zeile aus oder entfernen sie (macht nur für TCP-Verbindungen Sinn) und fügen die Zeilen listen.owner (definiert den Besitzer des Sockets), listen.group (definiert die Gruppe des Sockets) und listen.mode (definiert die Berechtigungen des Sockets) hinzu:

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = /var/run/php5-fpm/example.com.sock ;listen.allowed_clients = 127.0.0.1 listen.owner = web1 listen.group = client0 listen.mode = 0660 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Laden Sie PHP-FPM danach neu:

/etc/init.d/php5-fpm reload

Schauen Sie sich das Verzeichnis /var/run/php5-fpm an:

ls -l /var/run/php5-fpm

Sie sollten dort den Socket example.com.sock mit den Berechtigungen 0660 finden, der dem Benutzer web1 und der Gruppe client0 gehört:

root@server1:~# ls -l /var/run/php5-fpm  
 total 0  
 srw-rw---- 1 web1 client0 0 2011-09-21 11:08 example.com.sock  
 root@server1:~#

Schließlich müssen wir die fastcgi_pass-Zeile in unserem nginx-vhost auf fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; ändern:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Laden Sie nginx danach neu:

/etc/init.d/nginx reload

Das war’s!

4 Links

Über den Autor

Falko Timme ist der Eigentümer von Timme Hosting (ultra-schnelles nginx-Webhosting). Er ist der Hauptbetreuer von HowtoForge (seit 2005) und einer der Hauptentwickler von ISPConfig (seit 2000). Er hat auch zum O’Reilly-Buch “Linux-Systemadministration” beigetragen.

Share: X/Twitter LinkedIn

Erhalte neue Beiträge in deinem Posteingang.

Kein Spam. Jederzeit abmelden.