Kategoriearchive: Debian

„SHELL“ und „PATH“ für alle Cronjobs definieren

Ein kleiner Hinweis im Bezug auf einen weiteren Beitrag von heute.

Wer einige Scripts via Cronjob ausführt, wird wahrscheinlich – wie ich – die Shell sowie den „Path“ als Variable im Script selber definieren. Sollen aber Bash-spezifische Befehle in einer Zeile ausgeführt werden, bleibt „eigentlich“ kein Platz für das Definieren von Shell und „Path“.

Abhilfe verschafft man sich durch das definieren im Kopf der Cron-Datei. Wie gewohnt „crontab -e“ ausführen, um einen Cronjob anzulegen:

crontab -e

Ganz oben nun Folgendes ergänzen:

SHELL=/bin/bash
PATH=/usr/local/bin:/usr/local/sbin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/bin/X11

Unten wie gewohnt die Cronjobs eintragen. Ein Bespiel für einen Cronjob, der ohne obige Definition nicht funktionieren würde:

0 10 * * * /home/X/script.sh "`echo -e "Zeile eins\nZeile zwei"`"

Die gesamte Cron-Datei (z.B. „/var/spool/cron/crontabs/user“):

# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/crontab.txt installed on Sat Dec 21 18:22:45 2013)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/local/sbin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/bin/X11
0 10 * * * /home/X/script.sh "`echo -e "Zeile eins\nZeile zwei"`"

WordPress mit naxsi

Naxsi (Logo von MARE system)

Changelog

  • 24.10.2014 – Merged latest rules from github
  • 29.12.2013 – Merged latest rules from github
  • 29.12.2013 – Fix for „wp-admin/customize.php“

Ich habe mittlerweile einen Beitrag zum erstellen eigener Whitelists erstellt.

Naxsi ist die Web Application Firewall von nginx. Sie läuft als Modul mit nginx und ist im Gegensatz zu anderen Lösungen sehr einfach zu konfigurieren. Ebenfalls gibt es einen „Lernmodus“, um Regeln quasi anzulernen. Hierzu erschien im Admin Magazin ein schöner Artikel, der sich auch zu kaufen lohnt.

Mehr oder weniger durch Zufall bin ich auf ein fertiges Regelwerk gestoßen, um WordPress mit naxsi abzusichern. Die Quelle befindet sich hier.

Voraussetzung ist natürlich, dass nginx bereits installiert ist. Das Meta-Paket „nginx“ beinhaltet naxsi nicht, jedoch kann nginx mit dem Modul naxsi einfach nachinstalliert werden, die Konfigurationen bleiben erhalten und werden nicht überschrieben:

sudo apt-get install nginx-naxsi

In der Datei /etc/nginx/nginx.conf befand sich bereits ein Parameter für naxsi, der nun aktivieret werden kann:

/etc/nginx/nginx.conf:

http {
...
        include /etc/nginx/naxsi_core.rules;
...
}

Naxsi sieht vor, für jede Location im Server-Abschnitt ein eigenes Regelwerk zu verwenden. Dazu wird nun die jeweilige Site Konfiguration geöffnet, die WordPress beinhaltet. Zusätzlich wird eine neue Location angelegt, in die der Besucher im Falle weitergeleitet wird.

/etc/nginx/sites-available/default (Beispiel!):

server {
...
        location / {
                include    /etc/nginx/naxsi_wp.rules;
                ...
        }

        location /Denied {
                return 500;
        }
...
}

In diesem Fall befinden sich die Regeln – logischerweise – in der Datei „/etc/nginx/naxsi_wp.rules“.
Die Datei herunterladen:

wget http://debinux.de/wp-content/uploads/naxsi.rules -O /etc/nginx/naxsi_wp.rules

Zur Übersicht/Vollständigkeit hier der Code:

# Sample rules file for default vhost.
 
#LearningMode;
SecRulesEnabled;
#SecRulesDisabled;
DeniedUrl "/Denied";
 
## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
 
# WordPress naxsi rules

### HEADERS
BasicRule wl:1000,1001,1005,1007,1010,1011,1013,1100,1200,1308,1309,1310,1311,1315 "mz:$HEADERS_VAR:cookie";
# xmlrpc
BasicRule wl:1402 "mz:$HEADERS_VAR:content-type";

### simple BODY (POST)
# comments
BasicRule wl:1000,1010,1011,1013,1015,1200,1310,1311 "mz:$BODY_VAR:post_title";
BasicRule wl:1000 "mz:$BODY_VAR:original_publish";
BasicRule wl:1000 "mz:$BODY_VAR:save";
BasicRule wl:1008,1010,1011,1013,1015 "mz:$BODY_VAR:sk2_my_js_payload";
BasicRule wl:1001,1009,1005,1016,1100,1310 "mz:$BODY_VAR:url";
BasicRule wl:1009,1100 "mz:$BODY_VAR:referredby";
BasicRule wl:1009,1100 "mz:$BODY_VAR:_wp_original_http_referer";
BasicRule wl:1000,1001,1005,1008,1007,1009,1010,1011,1013,1015,1016,1100,1200,1302,1303,1310,1311,1315,1400 "mz:$BODY_VAR:comment";
BasicRule wl:1100 "mz:$BODY_VAR:redirect_to";
BasicRule wl:1000,1009,1315 "mz:$BODY_VAR:_wp_http_referer";
BasicRule wl:1000 "mz:$BODY_VAR:action";
BasicRule wl:1001,1013 "mz:$BODY_VAR:blogname";
BasicRule wl:1015,1013 "mz:$BODY_VAR:blogdescription";
BasicRule wl:1015 "mz:$BODY_VAR:date_format_custom";
BasicRule wl:1015 "mz:$BODY_VAR:date_format";
BasicRule wl:1015 "mz:$BODY_VAR:tax_input%5bpost_tag%5d";
BasicRule wl:1015 "mz:$BODY_VAR:tax_input[post_tag]";
BasicRule wl:1100 "mz:$BODY_VAR:siteurl";
BasicRule wl:1100 "mz:$BODY_VAR:home";
BasicRule wl:1000,1015 "mz:$BODY_VAR:submit";
# news content matches pretty much everything
BasicRule wl:0 "mz:$BODY_VAR:content";
BasicRule wl:1000 "mz:$BODY_VAR:delete_option";
BasicRule wl:1000 "mz:$BODY_VAR:prowl-msg-message";
BasicRule wl:1100 "mz:$BODY_VAR:_url";
BasicRule wl:1001,1009 "mz:$BODY_VAR:c2c_text_replace%5btext_to_replace%5d";
BasicRule wl:1200 "mz:$BODY_VAR:ppn_post_note";
BasicRule wl:1100 "mz:$BODY_VAR:author";
BasicRule wl:1001,1015 "mz:$BODY_VAR:excerpt";
BasicRule wl:1015 "mz:$BODY_VAR:catslist";
BasicRule wl:1005,1008,1009,1010,1011,1015,1315 "mz:$BODY_VAR:cookie";
BasicRule wl:1101 "mz:$BODY_VAR:googleplus";
BasicRule wl:1007 "mz:$BODY_VAR:name";
BasicRule wl:1007 "mz:$BODY_VAR:action";
BasicRule wl:1100 "mz:$BODY_VAR:attachment%5burl%5d";
BasicRule wl:1100 "mz:$BODY_VAR:attachment_url";
BasicRule wl:1001,1009,1100,1302,1303,1310,1311 "mz:$BODY_VAR:html";
BasicRule wl:1015 "mz:$BODY_VAR:title";
BasicRule wl:1001,1009,1015 "mz:$BODY_VAR:recaptcha_challenge_field";
BasicRule wl:1011 "mz:$BODY_VAR:pwd";
BasicRule wl:1000 "mz:$BODY_VAR:excerpt";

### BODY|NAME
BasicRule wl:1000 "mz:$BODY_VAR:delete_option|NAME";
BasicRule wl:1000 "mz:$BODY_VAR:from|NAME";

### Simple ARGS (GET)
# WP login screen
BasicRule wl:1100 "mz:$ARGS_VAR:redirect_to";
BasicRule wl:1000,1009 "mz:$ARGS_VAR:_wp_http_referer";
BasicRule wl:1000 "mz:$ARGS_VAR:wp_http_referer";
BasicRule wl:1000 "mz:$ARGS_VAR:action";
BasicRule wl:1000 "mz:$ARGS_VAR:action2";
# load and load[] GET variable
BasicRule wl:1000,1015 "mz:$ARGS_VAR:load";
BasicRule wl:1000,1015 "mz:$ARGS_VAR:load[]";
BasicRule wl:1015 "mz:$ARGS_VAR:q";
BasicRule wl:1000,1015 "mz:$ARGS_VAR:load%5b%5d";

### URL
BasicRule wl:1000 "mz:URL|$URL:/wp-admin/update-core.php";
BasicRule wl:1000 "mz:URL|$URL:/wp-admin/update.php";
# URL|BODY
BasicRule wl:1009,1100 "mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_http_referer";
BasicRule wl:1016 "mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect";
BasicRule wl:11 "mz:$URL:/xmlrpc.php|BODY";
BasicRule wl:11 "mz:$URL:/wp-cron.php|BODY";
BasicRule wl:2 "mz:$URL:/wp-admin/async-upload.php|BODY";
# URL|BODY|NAME
BasicRule wl:1100 "mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_original_http_referer|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/user-edit.php|$BODY_VAR:from|NAME";
BasicRule wl:1100 "mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:attachment%5burl%5d|NAME";
BasicRule wl:1100 "mz:$URL:/wp-admin/post.php|$BODY_VAR:attachment_url|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/plugins.php|$BODY_VAR:verify-delete|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/post.php|$BODY_VAR:post_category[]|NAME";
BasicRule wl:1311 "mz:$URL:/wp-admin/post.php|$BODY_VAR:post_category|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/post.php|$BODY_VAR:tax_input[post_tag]|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/post.php|$BODY_VAR:newtag[post_tag]|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/users.php|$BODY_VAR:users[]|NAME";
# URL|ARGS|NAME
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/load-scripts.php|$ARGS_VAR:load[]|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/users.php|$ARGS_VAR:delete_count|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/users.php|$ARGS_VAR:update|NAME";

# plain WP site
BasicRule wl:1000 "mz:URL|$URL:/wp-admin/update-core.php";
BasicRule wl:1000 "mz:URL|$URL:/wp-admin/update.php";
# URL|BODY
BasicRule wl:1009,1100 "mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_http_referer";
BasicRule wl:1016 "mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect";
BasicRule wl:11 "mz:$URL:/xmlrpc.php|BODY";
BasicRule wl:11 "mz:$URL:/wp-cron.php|BODY";
# URL|BODY|NAME
BasicRule wl:1100 "mz:$URL:/wp-admin/post.php|$BODY_VAR:_wp_original_http_referer|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/post.php|$BODY_VAR:metakeyselect|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/user-edit.php|$BODY_VAR:from|NAME";
BasicRule wl:1100 "mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:attachment%5burl%5d|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/admin-ajax.php|$BODY_VAR:data[wp-auth-check]|NAME";
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/update-core.php|$BODY_VAR:checked[]|NAME";

# URL|ARGS|NAME
BasicRule wl:1310,1311 "mz:$URL:/wp-admin/load-scripts.php|$ARGS_VAR:load[]|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/users.php|$ARGS_VAR:delete_count|NAME";
BasicRule wl:1000 "mz:$URL:/wp-admin/users.php|$ARGS_VAR:update|NAME";

# WP 3.6/8 Fix
BasicRule wl:1100 "mz:$URL:/|$ARGS_VAR:statify_referrer";
BasicRule wl:1001,1015,1009,1311,1310,1101,1016 "mz:$URL:/|$BODY_VAR:customized";
### Plugins
#WP Minify
BasicRule wl:1015 "mz:$URL:/wp-content/plugins/bwp-minify/min/|$ARGS_VAR:f";

Via „naxsi_wp.rules“ wird /Denied als Umleitung für geblockte Anfragen definiert. „return 500“ würde ergo auf die Fehlerseite 500 verweisen. Selbstverständlich sind diese Pfade und Parameter nach Belieben einzustellen. Wer witzig ist, darf auch 418 zurückgeben. ;)

Zur Aktivierung den Dienst neustarten:

service nginx restart

Achtung: Bei WordPress Updates kann es passieren, dass die Regeln geändert werden müssen! Ich habe die obigen Regeln erfolgreich auf WordPress 3.8 getestet. Sollte etwas nicht mehr funktionieren, empfehle ich den Lernmodus („LearningMode“) zu aktivieren und das Wiki zu naxsi zu studieren. Ein Blick in die Funktionsweise schadet generell nicht und sollte in jedem Fall schon geschehen sein.

Hat naxsi etwas gefiltert, so wird dies in der Fehler-Logdatei (via „ErrorLog“) des jeweiligen Servers festgehalten.
Getestet werden kann die Konfiguration und Wirksamkeit, indem „?a=<>“ an eine WordPress URL angehängt wird, z.B. www.domain.tld/?a=<>
Der Fehler wird protokolliert:

NAXSI_FMT: ip=X.X.X.X&server=www.domain.tld&uri=/&learning=0&total_processed=8&total_blocked=2&zone0=ARGS&id0=1302&var_name0=a, client: X.X.X.X, server: debinux.de, request: „GET /?a=%3C%3E HTTP/1.1“, host: „www.domain.tld“


Das naxsi-Logo ist Eigentum von MARE system.

Script: Rekursiv Bilder verlustfrei optimieren (PNG / JPEG und mehr)

Ich möchte gerne ein kleines Script teilen, das rekursiv einen Ordner nach Bilddateien im Format PNG, JPEG, JPG, GIF, TIFF sowie BMP durchsucht und diese verlustfrei optimiert.
Das Script arbeitet leise, legt aber einen definierten Log-Ordner an, in dem die Ausgaben komprimiert abgelegt werden.

Für JP(E)G werden außerdem alle Kommentare sowie Exif Daten entfernt.
Bereits optimierte Bilder werden übersprungen. Also keine Sorge, dass bei täglicher Ausführung die Qualität nach einem Monat am Boden ist. Schließlich wird optimiert, nicht immer wieder komprimiert.

Abhängigkeiten

Unter Debian/Ubuntu können die Abhängigkeiten folgendermaßen installiert werden:

sudo apt-get install jpegoptim optipng

Die Pakete sollten sich in allen gängigen Distributionen finden, alternativ können diese auch „from Source“ installiert werden. Das empfiehlt sich vor allem dann, wenn die aktuellsten Versionen verwendet werden möchten. Den Quellcode kann man sich auf der jeweiligen Entwickler Website herunterladen:

http://optipng.sourceforge.net/
http://freecode.com/projects/jpegoptim

Das Script

Code

#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

####
SCANDIR="/var/www/images"
LOGDIR="/var/log/imageopt"
####

[ -d $LOGDIR ] || mkdir -p $LOGDIR
DATE=`date +%d%m%Y_%H%M`

for file in `find $SCANDIR -name *.png -o -name *.bmp -o -name *.gif -o -name *.tiff `; do optipng -quie$
gzip $LOGDIR/optipng_$DATE.log

for file in `find $SCANDIR -name *.jpg -o -name *.jpeg`; do jpegoptim --strip-all -t $file > $LOGDIR/jpe$
gzip $LOGDIR/jpegoptim_$DATE.log

Einzustellen ist der „SCANDIR“ Paramenter. Dieses Verzeichnis wird rekursiv durchsucht.
Falls gewünscht, kann außerdem der Pfad der Log-Dateien geändert werden. Die Log-Dateien werden im Anschluss an das Optimieren komprimiert gespeichert.

Download und Installation

wget http://www.debinux.de/wp-content/uploads/2013/11/image_opt.zip
unzip image_opt.zip 
mv image_opt.sh /usr/local/bin/image-opt
chmod +x /usr/local/bin/image-opt
nano /usr/local/bin/image-opt # Zum Einstellen der Optionen
image-opt # Falls gewünscht manuell ausführen

Automatisches optimieren

Sollen Bilder automatisch jeden Tag um 1 Uhr Morgens optimiert werden, so wird einfach ein Cron-Job hierfür angelegt.

Dazu „sudo crontab -e“ ausführen und am Ende der Datei Folgendes einfügen:

0 1 * * * /bin/bash /usr/local/bin/image-opt

Nach dem Speichern ist der Job installiert.

Wichtiger Hinweis: Sollte dieses Script eingesetzt werden, um WordPress Bild-Dateien zu optimieren, beachtet, dass im Anschluss – quasi als letzte Zeile – die Rechte der Dateien wieder an „www-data“ (oder wer auch immer den Webdienst ausführt) vergeben werden, z.B. …

chown -R www-data: /var/www/XYZ/wordpress/wp-content/uploads

AfterLogic WebMail Lite: Simple Webmail Alternative zu Horde, Roundcube und Co.

WebMail Lite Logo

Lange war ich auf der Suche nach einem sehr simplen Webmail Client ohne viel drum und dran. Einzig und allein für den Notfall, um schnell auf E-Mails zugreifen zu können.
Nun habe ich endlich den Richtigen für mich gefunden. Wichtig war mir, dass dieser nicht veraltet ist und sich in aktiver Entwicklung befindet.

WebMail Lite (c) www.afterlogic.com
WebMail Lite (c) www.afterlogic.com
„AfterLogic WebMail Lite“ ist definitiv ein Außenseiter unter den Webmail Clients. Abstriche müssen auch im Bezug auf eine mobile Version hingenommen werden. Für mich kein Problem, da ich via Smartphone sowieso auf den eingebauten E-Mail Client zugreifen werde.

Die Installationsumgebung

In meinem Fall wird der Webmail Client unter „/usr/share/nginx/webmail“ gespeichert. Unter Umständen bitte anpassen. Apache2 wird gewöhnlich mit einem Webroot „/var/www“ ausgeliefert.
PHP- und Webserver-Prozess laufen als Benutzer „www-data“.
Ich gehe davon aus, dass folgendes bereits installiert wurde:

– Ein Webserver (…)
– MySQL Server
– PHP Anbindung

Installation

Hinweis: Es geht hier nur um die Grundkonfiguration. Bei Zeit werde ich erweiterte Funktionen ergänzen!

Der Client ist in seinen Abhängigkeiten sehr dünn, lediglich ein PHP Modul muss noch installiert werden. Zum Entpacken des Downloads, der nur als zip-Datei angeboten wird, benötigen wir noch „unzip“:

sudo apt-get install php5-curl unzip

Das Verzeichnis erstellen, in dieses wechseln, herunterladen/entpacken des Clients und aufräumen:

mkdir /usr/share/nginx/webmail
cd /usr/share/nginx/webmail
wget http://www.afterlogic.com/download/webmail_php.zip
unzip webmail_php.zip
rm changelog.txt readme.txt webmail_php.zip
mv webmail/* .
rm -r webmail
chown -R www-data:www-data /usr/share/nginx/*

mysql -u root -pMYSQL-ROOT-PASSWD ausführen (Achtung: nach -p KEIN Leerzeichen) und folgendes einfügen. PASSWORT unten durch ein eigenes Passwort für die Webmail Datenbank ändern:

CREATE DATABASE webmaildb;
GRANT ALL PRIVILEGES ON webmaildb.* TO 'webmailusr'@'localhost' IDENTIFIED BY 'PASSWORT';
FLUSH PRIVILEGES;

Die Datenbank „webmaildb“ wurde jetzt erstellt. Der Benutzer „webmailusr“ darf diese verwalten.
Mit „exit“ die MySQL Kommandozeile beenden.

http://domain.tld/webmail/install aufrufen. Die Abhängigkeiten noch einmal überprüfen und die Konfiguration durch einen Klick auf „Next“ beginnen:

Beginn der Konfiguration
Beginn der Konfiguration

Mit „Test database“ sichergehen, dass die Anbindung besteht. Eingetragen werden natürlich die Daten, mit denen zuvor die Datenbank erstellt wurde.
Nach einem Klick auf „Next“ nun ein Passwort für den Administrator („mailadm“) erstellen und fortfahren.

In der nächsten Maske beide Haken für „IMAP4“ und „SMTP“ setzen:

Administrator Konfiguration
Administrator Konfiguration

Da die Installation nun abgeschlossen ist, das Verzeichnis „install“ löschen:

rm -r /usr/share/nginx/webmail/install

Sieve

Ist Sieve eingerichtet, beispielsweise via Dovecot, kann dieses hier ebenfalls noch aktiviert werden. Dazu die Datei „webmail/data/settings/config.php“ öffnen und verändern:

$aSieveDomains = array('imap.domain.tld');
return array(

        'sieve' => true,
        'sieve.autoresponder' => true,
        'sieve.forward' => true,
        'sieve.filters' => true,
        'sieve.config.host' => '',
        'sieve.config.port' => 4190,
        'sieve.config.filters-folder-charset' => 'utf-8', // [utf7-imap, utf-8]
        'sieve.config.domains' => $aSieveDomains

);

Unbedingt „’sieve‘ => true“ setzen!
Dovecot benutzt – jedenfalls unter Debian Wheezy – bereits den neueren Port 4190, oben schon geändert. Bitte überprüfen, ob euer Sieve das auch tut. Bis auf die IMAP-Adresse im Array „aSieveDomains“ ist weiter nichts mehr zu ändern.

Benutzung

Einloggen mit dem E-Mail Login unter http://domain.tld/webmail, um E-Mails abzurufen.
Via http://domain.tld/webmail/adminpanel das Panel administrieren. Beispielsweise die Sprache umstellen oder das Zeitformat/die Zeitzone ändern.

How-To: nginx, PHP-FPM (multiple Sockets), MySQL und Subdomains in Debian Wheezy/Jessie

Im Gegensatz zu dem Klassiker „LAMP“ (Linux, Apache, MySQL und PHP), beschreibe ich hier die Methode „LNMP“, also weniger Apache, mehr nginx. Als kleine Zugabe soll jeder Server (jede Site/Subdomain) einen eigenen PHP Socket zugeteilt bekommen und durch „open_basedir“ der Zugriff auf die eigene Site limitiert werden.

Changelog

  • 07.06.2015 – PHP: Fix für Debian Jessie (danke an Kevin für den Hinweis!)
  • 03.08.2014 – „date.timezone“ und „listen.allowed_clients“ gesetzt
  • 17.01.2014 – Kritische Funktionen deakvieren
  • 18.05.2014 – „listen.*“-Parameter für PHP5-FPM, welche nun Pflicht sind

Das Szenario

Um alles ein wenig Anschaubarer zu machen, gehe ich von folgendem Umstand aus:

Das Szenario
Das Szenario

Es wird davon ausgegangen, dass für die Domäne „domain.tld“ ein A-Record „*“ besteht, welcher alle Anfragen auf die öffentliche IP des Webservers leitet. Während des Schreibens habe ich meine lokale „hosts“ Datei („/etc/hosts“) abgeändert, um die Konfiguration zu testen, wobei „192.168.67.129“ die IP meines Test-Servers ist. Zum Ausprobieren ohne Domäne sehr hilfreich:

192.168.67.129 domain.tld
192.168.67.129 www.domain.tld
192.168.67.129 notavailable.domain.tld
192.168.67.129 custom1.domain.tld
192.168.67.129 custom2.domain.tld

Wie bereits beschrieben erhalten alle Sites einen eigenen PHP Socket mit zusätzlichem Attribut für das „open_basedir“.

Installation

Es reicht grundsätzlich die Installation folgender Pakete aus:

apt-get install nginx mysql-server mysql-client php5-fpm php5-mysql

Während der Installation von MySQL wird ein Passwort für den root-Benutzer abgefragt, welches frei wählbar ist aber sich von dem des lokalen root-Benutzers Unterscheiden sollte.

Natürlich gibt es verschiedene nginx Pakete mit unterschiedlichen Funktionen. Das Paket „nginx“ installiert z.B. „nginx-full“. Eine Übersicht gibt es hier

Abhängig von der später eingesetzten Software (Drupal, Joomla, WordPress, Webmail etc.), können noch weitere PHP-Module benötigt werden. Diese Abhängigkeiten werden natürlich in den Dokumenten der Anbieter beschrieben. „php5-mysql“ ist das Modul zur Anbindung an die die MySQL Datenbank.

Konfiguration

Ein kleines bisschen Sicherheit

Es ist immer zu empfehlen, die Version des Webservers und PHP zu verstecken. Selbstverständlich werden so keine Angriffe verhindert, allerdings kann das Auffinden von anfälligen (veralteten) Versionen erschwert werden. Folgende zwei Dateien dazu editieren:

/etc/nginx/nginx.conf:

server_tokens off;

/etc/php5/fpm/php.ini:

expose_php = Off

Für alle Sockets sollten zudem kritische Funktionen zum Ausführen von Anwendungen deaktiviert werden. Diese können für einzelne Sockets bei Bedarf freigegeben werden:

/etc/php5/fpm/php.ini:

disable_functions=phpinfo,exec,shell_exec,system,passthru

Auch MySQL kann schnell minimal abgesichert werden, dazu „mysql_secure_installation“ ausführen:

Enter current password for root (enter for none): Passwort von der Installation des MySQL eingeben
Change the root password? [Y/n] n
Alles Weitere mit Enter bestätigen

Einrichtung der Sites

Zuerst die Sites bereinigen:

rm /etc/nginx/sites-*/*

Und im Anschluß vier neue Sites anlegen (z.B. via „nano“):

/etc/nginx/sites-available/default:

server {
       listen         80;
       rewrite        ^ http://www.domain.tld permanent;
}

/etc/nginx/sites-available/www:

server {
        listen 80;

        root /usr/share/nginx/www;
        access_log /var/log/nginx/access_log_www;
        index index.html index.htm index.php;

        server_name domain.tld www.domain.tld;

        location / {
        }

        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm-www.sock;
                fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

/etc/nginx/sites-available/custom1:

server {
        listen 80;

        root /usr/share/nginx/custom1;
        access_log /var/log/nginx/access_log_custom1;
        index index.html index.htm index.php;

        server_name custom1.domain.tld;

        location / {
        }

        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm-custom1.sock;
                fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

/etc/nginx/sites-available/custom2:

server {
        listen 80;

        root /usr/share/nginx/custom2;
        access_log /var/log/nginx/access_log_custom2;
        index index.html index.htm index.php;

        server_name custom2.domain.tld;

        location / {
        }

        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm-custom2.sock;
                fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

Hinweis: Durch „server_name“ reagieren die Sites nur auf eben ihren Namen.

Nun die Sites für nginx sichtbar machen:

cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/* .

Wichtig: Die Site „default“ ist die Anlaufstelle für Aufrufe der IP-Adresse oder unbekannten Sub-Domains. Für unsere Konfiguration werden diese Aufrufe auf www.domain.tld umgeleitet, dafür steht der „redirect“.

Einrichtung der PHP Sockets

Der PHP FastCGI Process Manager (php-fpm) durchsucht das Verzeichnis „/etc/php5/fpm/pool.d“ nach zu erstellenden Sockets. Alle vorhandenen vorab löschen:

rm /etc/php5/fpm/pool.d/*

Drei neue Konfigurationen werden hier erstellt, die „Listener“ entsprechen denen aus den Sites:

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

[www]
listen = /var/run/php5-fpm-www.sock
listen.backlog = 4096
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.allowed_clients = 127.0.0.1
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 40
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_admin_value[open_basedir] = /usr/share/nginx/www/:/tmp/
php_admin_value[date.timezone] = Europe/Berlin

/etc/php5/fpm/pool.d/custom1.conf:

[custom1]
listen = /var/run/php5-fpm-custom1.sock
listen.backlog = 4096
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.allowed_clients = 127.0.0.1
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 40
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_admin_value[open_basedir] = /usr/share/nginx/custom1/:/tmp/
php_admin_value[date.timezone] = Europe/Berlin

/etc/php5/fpm/pool.d/custom2.conf:

[custom2]
listen = /var/run/php5-fpm-custom2.sock
listen.backlog = 4096
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.allowed_clients = 127.0.0.1
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 40
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_admin_value[open_basedir] = /usr/share/nginx/custom2/:/tmp/
php_admin_value[date.timezone] = Europe/Berlin

Das Limit der Backlogs kann nicht einfach beliebig erhöht werden, das System hat hierfür ein globales Limit (Standard: 1000), welches in der Datei /etc/sysctl.conf eingestellt werden kann:

echo "net.core.netdev_max_backlog=4096" >> /etc/sysctl.conf
echo "net.core.somaxconn=4096" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog=4096" >> /etc/sysctl.conf
sysctl -p # Änderungen einlesen

Die „pm“ Attribute haben großen Einfluss auf die Leistung und Resourcenverwendung von PHP auf das System. Damit auch auf die Website. Zur besseren Konfiguration nach Bedarf einfach Google bemühen, da eine Erklärung zu komplex für dieses How-To wäre. Läuft auf der Site „custom1“ beispielsweise eine weitaus komplexere Anwendung (~Joomla) als auf „custom2“ (~Postfixadmin), sollten die Einstellungen natürlich angepasst werden.
Kleiner Hinweis: Bei sehr, sehr vielen Sites mit vielen Prozessen, könnte irgendwann auch die Einstellung „fs.file-max“ in der Datei „/etc/sysctl.conf“ wichtig werden.

Die „php_admin_value“ Attribute können ebenfalls eingesetzt werden, um die globalen Einstellungen aus der Datei „/etc/php5/fpm/php.ini“ zu überschreiben. Zum Beispiel lassen sich eigene memory_limits einrichten.

Verzeichnisse und Dateien anlegen

Die Verzeichnisse der Sites dieses Beispiels liegen unter „/usr/share/nginx“.
Achtung: Möchte man einen anderen Ort hierfür wählen, müssen die Sockets („open_basedir“) sowie die Site Konfigurationen angepasst werden!

Das Webroot bereinigen und die neuen Ordner für die Sites anlegen, abschließend den Besitzer auf www-data abändern:

rm -r /usr/share/nginx/*
mkdir /usr/share/nginx/{www,custom1,custom2}
chown -R www-data:www-data /usr/share/nginx/*

Access- und Error-Log

Der Speicherort ist selbstverständlich frei wählbar, allerdings muss das Verzeichnis exisiteren, damit die Dateien von nginx automatisch erstellt werden.

In den Site Konfigurationen ist der Speicherort „/var/log/nginx“ für die Access Logs jedoch gezielt gewählt, da hier die automatisch mit der Installation von nginx erzeugte Regel für „logrotate“ greift. Die Regel befindet sich in der Datei „/etc/logrotate.d/nginx“. Wird der Speicherort also geändert, sollte man sich Gedanken über das rotieren der Logs machen, damit diese nicht den Speicherplatz unnötig belegen. Auch die Übersicht leidet nach einigen Monaten stark. Die vorhandene Regel kann hierzu als Beispiel dienen.

Es ließe sich zusätzlich das „error_log“ per Site definieren, worauf ich in diesem Beispiel verzichtet habe. Die Fehler werden in die global via „/etc/nginx/nginx.conf“ definierte Datei geschrieben. Der Pfad lautet in der Standard-Installation „/var/log/nginx/error_log“.

Der Site „default“ wurde kein solches Attribut erteilt, weshalb hierfür auch die Zugriffe in die globale Datei „/var/log/nginx/access_log“ geschrieben werden.

Fertigstellung

Die Dienste durchstarten

Abschließend die Änderungen übernehmen, indem die zugehörigen Dienste neugestartet werden:

service php5-fpm restart
service nginx restart

Konfiguration testen

Um ganz sicher zu gehen, dass nun alles funktioniert, kann in den Webroots jeweils eine Datei „index.php“ angelegt werden, die die PHP Konfiguration ausgibt („phpinfo“) sowie den Sitename anzeigt:

echo "Sitename CUSTOM1 

" > /usr/share/nginx/custom1/index.php echo "Sitename CUSTOM2

" > /usr/share/nginx/custom2/index.php echo "Sitename WWW

" > /usr/share/nginx/www/index.php

Sieht nicht schön aus, aber erfüllt seinen Zweck.

Zusätzlich die „Worker“ überprüfen:

ps aux | grep php-fpm

ps aux | grep php-fpm
ps aux | grep php-fpm
Die Ausgabe zeigt, dass die definierten Worker auch tatsächlich arbeiten. Da die Konfiguration gerade neugestartet wurde und die Prozesse noch nicht ausgelastet sind, entspricht die Anzahl dem Attribut „pm.start_servers“, nämlich 3.

Viel Erolg!