Gallery3 und nginx auf Debian/wheezy

Ich bin ja vor kurzem mit allen meinen Servern von Apache 2.2 zu nginx migriert. Im Prinzip waren da keine größeren Blocker dabei, aber ein paar Applikationen, vor allem natürlich komplexerer PHP-Kram und alles, was CGIs benötigt, habe ich doch etwas gebraucht, vor allem, weil die Konfigurationsbeispiele, die ich im Internet gefunden habe, nicht so wirklich “idiomatisch” - read: unsauber - waren. Eines der Dinge, die unter dem Titel Check If File Exists im nginx-Wiki zu finden ist, ist das hier, was so halt aus der Apache-Welt stammt:

if (!-e $request_filename) {
  rewrite (.*) /bar/$1
}

nginx bietet dafür die Anweisung try_files, welche effizienter ist. Obiger Block neu geschrieben wäre:

try_files $uri /bar/$uri

Die meisten alten Konfigurationen, die ich gefunden habe, halten sich nicht an diese Empfehlungen oder sind allgemein eher mau (bei der vorhin verlinkten z.B. kann man auch ohne Authentifizierung auf alle Bilder zugreifen, falls man deren Pfad kennt). Ich habe versucht, das besser zu machen, kläre aber gerade nochmal ein paar Details auf der nginx Mailingliste.

Wenn man mit nginx Zugriff auf PHP braucht, dann macht man das zumeist über das FastCGI-Modul von nginx und benutzt als php-fpm als Prozess-Manager für PHP. Auf einem aktuellen Debian/wheezy ist das alles schon gepackaged, und angenommen, man hat bereits alle PHP-Pakete, die Gallery3 so braucht, installiert, so reicht es, ein apt-get install php5-fpm abzusetzen. Das Debian-Paket installiert seine eigene Version der PHP-Konfiguration unter /etc/php5/fpm/php.ini. Man sollte darauf achten, dass in diesem File die folgende Direktive gesetzt ist:

cgi.fix_pathinfo=0

Warum steht im erwähnten “Pitfalls”-Artikel.

PHP-FPM kennt das Konzept von “Worker-Pools”, welche im Verzeichnis /etc/php5/fpm/pool.d angelegt werden können. In meinem Beispiel sei der Host, unter dem das Gallery3 laufen soll gallery3.example.com, weswegen ich die Datei /etc/php5/fpm/pools.d/gallery3.conf mit folgendem Inhalt angelegt habe:

[gallery3]
user = vhost-user
group = vhost-group
listen = /tmp/gallery3-example-com.sock
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 5
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500

Wie man sieht sehr übersichtlich, da kann man einiges mehr anziehen, für meine bescheidenen Bedürfnisse reichen die Anzahl an Workern etc. aber aus. Man sieht übrigens, dass hier ein Äquivalent zum suexec-Mechanismus implementiert ist, das PHP wird hier als User vhost-user und Gruppe vhost-group ausgeführt (hier ginge sogar noch ein chroot-Eintrag…). Nach einem invoke-rc.d php5-fpm restart ist das PHP dann Einsatzbereit.

Bevor wir uns die Konfiguration des nginx-Server-Containers ansehen, müssen wir kurz darüber reden, warum Gallery3 so kompliziert ist. Im einzelnen muß man nämlich die folgenden Punkte beachten:

  1. /lib/images/logo.png - einfach durchreichen
  2. /Controller?params - auf /index.php?kohana_uri=Controller&params umschreiben
  3. /index.php/Controller?params - auf /index.php?kohana_uri=Controller&params umschreiben
  4. /var/(albums|thumbs|resizes) - auf /file_proxy/$1 umschreiben (und mit Punkt 2 weitermachen)
  5. Zugriff auf /var/(tmp|uploads|logs), /bin und diverse PHP/Websever-Konfigurationen (.inc.php, .htaccess) sperren
  6. Aus Geschwindigkeitsgründen Expires:-Header für statischen Content (wie z.B. .css, .png etc.) setzten

Die Herausforderung war, das alles sauber zu tun. Herausgekommen ist das hier:

# gallery3.example.com definition
server {
  server_name gallery3.example.com;
  listen 80;
  listen [::]:80;

  root /srv/www/http-gallery3-example-com;
  access_log /var/log/nginx/access-gallery3.example.com.log;
  index index.php;

  location / {
    location ~ /(index\.php/)?(.+)$ {
      try_files $uri /index.php?kohana_uri=$2&$args;

      location ~ /\.(ht|tpl(\.php?)|sql|inc\.php|db)$ {
        deny all;
      }

      location ~ /var/(uploads|tmp|logs) {
        deny all;
      }

      location ~ /bin {
        deny all;
      }

      location ~ /var/(albums|thumbs|resizes) {
        rewrite ^/var/(albums|thumbs|resizes)/(.*)$ /file_proxy/$2 last;
      }

      location ~* \.(js|css|png|jpg|jpeg|gif|ico|ttf)$ {
        try_files $uri /index.php?kohana_uri=$uri&$args;
        expires 30d;
      }
    }

    location = /index.php {
      fastcgi_split_path_info ^(.+\.php)(/.+)$;
      fastcgi_pass unix:/tmp/gallery3-example-com.sock;
      fastcgi_index index.php;
      fastcgi_param PATH_INFO $fastcgi_path_info;
      include fastcgi_params;
    }
  }
}

Um zu verstehen, warum und wie das funktioniert, ist es als erstes wichtig, sich die Dokumentation zur location-Direktive durchzulesen. Dröseln wir es also mal auf:

  • Der äussere location /-Block ins wahrscheinlich unnötig, das kläre ich gerade mit der nginx-Mailingliste.
  • Der location-Block location ~ /(index\.php/)?(.+)$ speichert in $1 den Controller-Namen und definiert mittels try_files $uri /index.php?kohana_uri=$2&$args, dass wenn das angeforderte File nicht gefunden wird, ein Rewriting auf /index.php?kohana_uri=Controller&params gemacht werden soll. An dieser Stelle ist es wichtig zu wissen, dass diese try_files-Direktive nur dann aktiv wird, wenn es innerhalb des “location”-Blocks keinen spezifischeren Match gibt - vgl. diesen Link Dies ermöglicht es uns, eine Sonderbehandlung für andere Requests zu definieren.
  • Die Blöcke mit deny all; sollten selbsterklärend sein. Wichtig ist zu wissen, dass mehrere Regexp-location-Blöcke in der Reihenfolge aktiv werden, in der sie matchen - first match wins. Die Ausnahme davon sind “spezifischere” Matches, also ^~ (URL fängt mit Regexp an), in diesem Fall wird nicht weiter gesucht und solche Anweisungen werden auch vor allen anderen Regexp-Matches ausgewertet.
  • Mit location ~ /var/(albums|thumbs|resizes) definieren wir das Rewriting auf den File-Proxy von Gallery3, der verhindert, dass wir uns Bilder anzeigen lassen können, wenn wir nicht eingeloggt sind.
  • Mit location ~* \.(js|css|png|jpg|jpeg|gif|ico|ttf)$ setzen wir die Expires:-Header. Es ist wichtig, dass dieser Block nach dem Block für den File-Proxy kommt, da wir sonst die Zugriffskontrolle wieder aushebeln.
  • Der Match mit der höchsten Priorität ist location = /index.php. Das ist ein exakter Match, der nur für /index.php greift, und er hat eine höhere Priorität als der Regexp-Block location ~ /(index\.php/)?(.+)$. Hier haben wir unseren PHP-FastCGI-Kram.

Wenn man neu mit nginx ist, dann ist das manchmal alles gar nicht so einfach, und so hat es mich auch insgesamt zwei Tage gekostet, die obige Konfiguration so hinzubekommen, dass ich zufrieden war. Nachdem es im Netz kaum akkzeptable Konfigurationen gibt dachte ich mir, ich lasse Euch da mal teilhaben. Hoffentlich hilft’s Euch ;-)