nginx + symfony als Unterverzeichnis
Konfiguration von nginx mit einer symfony app als Unterverzeichnis.
Datum: 2020-05, Tags: Linux, nginx, symfony, subdirectory, nestedAus unerfindlichen Gründen ist es unerwartet kompliziert, nginx so zu konfigurieren, dass Webseiten bzw. apps als Unterverzeichnis betrieben werden. Damit meine ich speziell symfony-apps mit php-fpm. Die Motivation für eine derartige Konfiguration kann durchaus unterschiedlich sein, z.B. weil man "unwichtige" webseiten mit nur einem SSL-Zertifikat betreiben will.
Ich will die dafür notwendigen EInstellungen hier einmal durchgehen und beschreiben, warum spezielle Einstellungen so gemacht werden. Mein Setup soll wie folgt aussehen:
- Ich verwende lokal nginx in der Version 1.16.1, sowie php-fmp in der Version 7.2.5
- Meine "default" Server-Instanz soll unter dem hostnamen
apps.local
unter http://apps.local/ erreichbar sein. - Die "default" Instanz soll lediglich eine statische html-Seite
index.html
anzeigen. Das Verzeichnis dafür ist/var/www/apps.local
- Eine "subdir" Instanz soll unter http://apps.local/subdir/ erreichbar sein.
- Die "subdir" Instanz solle eine vollständige symfony app unter
/var/www/apps.local.subdir
sein. Da symfony mit einen "public" directory arbeitet, ist der tatsächliche Pfad zurindex.php
für nginx/var/www/apps.local.subdir/public
. - Ich habe mein nginx und php-fpm so eingestellt, dass die Prozesse unter meinem lokalen Benutzerkonto laufen. Damit muss ich mich nicht weiter um die Dateisystem-Berechtigungen kümmern, sondern kann alle Dateien als normaler Benutzer (ohne root-Berechtigungen) anlegen.
"default" Instanz
Los geht's. Erst einmal trage ich den gewünschten hostname in die Datei /etc/hosts
ein. Ich könnte auch mit
dem hostnamen localhost
arbeiten, das kommt mir aber eventuell mit anderen Konfigurationen in die Quere.
... 127.0.0.1 apps.local ...
Dann erzeuge ich dafür das Basisverzeichnis und erstelle dort eine Datei index.html
. Um sicherzustellen,
dass darin auch Unterverzeichnisse richtig verwendet werden, verweise ich auf eine CSS-Datei in /css/style.css
.
~mkdir /var/www/apps.local ~cd /var/www/apps.local
<html> <head> <link rel="stylesheet" href="/css/style.css"> </head> <body> <h1> Hello apps! </h1> </body> </html>
html { background-image: linear-gradient(160deg, #dfe8dd 0%, #1fc8db 51%, #2cb5e8 75%); color: #fff; text-align: center; height: 100%; margin: 0; } h1 { line-height: 5em; }
Dafür wird eine ganz einfache nginx-Konfiguration benötigt. Ich benutze das conf.d
Verzeichnis von
nginx, könnte aber auch sites-available
benutzen und die conf-Datei dann nach sites-enabled
verlinken.
Für die nginx-Konfiguration werden dann doch root-Berechtigungen benötigt.
server { server_name apps.local; root /var/www/apps.local; index index.html index.php; location / { try_files $uri /index.html; } error_log /var/log/nginx/apps.local_error.log; access_log /var/log/nginx/apps.local_access.log; }
Nach jeder Änderung an der nginx-Konfiguration muss der Dienst mit sudo service nginx reload
neu geladen werden,
das werde ich im Weiteren nicht mehr extra erwähnen! Nachdem nginx neu geladen wurde, ist meine "default" Instanz nun unter
http://apps.local/ erreichbar und auch das css wird richtig verarbeitet.
Mehr brauche und will ich nicht an dieser Stelle.
"subdir" Instanz
Ich erzeuge eine einfache symfoy Installation in das Verzeichnis /var/www/apps.local.subdir
. Dort will
ich für dieses Beispiel lediglich einen einfachen Controller benutzen, sowie assets über "webpack" einbinden. Die
Installation kürze ich hier ein wenig ab, die Symfony Dokumentation
beschreibt das im Detail. Ich denötige weiterhin das Maker Bundle,
Webpack Encore,
Twig,
sowie den Web Profiler.
/var/www/apps.localmkdir /var/www/apps.local.subdir /var/www/apps.localcd /var/www /var/wwwsymfony new apps.local.subdir * Creating a new Symfony project with Composer (running /usr/bin/composer create-project symfony/skeleton /var/www/apps.local.subdir) * Setting up the project under Git version control (running git init /var/www/apps.local.subdir) [OK] Your project is now ready in /var/www/apps.local.subdir /var/wwwcd apps.local.subdir /var/www/apps.local.subdircomposer require symfony/maker-bundle symfony/webpack-encore-bundle symfony/twig-pack symfony/profiler-pack Using version ^1.18 for symfony/maker-bundle Using version ^1.7 for symfony/webpack-encore-bundle Using version ^1.10 for doctrine/annotations Using version ^1.0 for symfony/twig-pack Using version ^1.0 for symfony/profiler-pack ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Restricting packages listed in "symfony/symfony" to "5.0.*" Package operations: 16 installs, 0 updates, 0 removals - Installing nikic/php-parser (v4.4.0): Loading from cache - Installing doctrine/inflector (1.4.1): Loading from cache - Installing symfony/maker-bundle (v1.18.0): Loading from cache - Installing twig/twig (v3.0.3): Loading from cache - Installing symfony/translation-contracts (v2.0.1): Loading from cache - Installing symfony/twig-bridge (v5.0.8): Loading from cache - Installing symfony/twig-bundle (v5.0.8): Loading from cache - Installing twig/extra-bundle (v3.0.3): Loading from cache - Installing symfony/twig-pack (v1.0.0): Loading from cache - Installing symfony/web-profiler-bundle (v5.0.8): Loading from cache - Installing symfony/stopwatch (v5.0.8): Loading from cache - Installing symfony/profiler-pack (v1.0.4): Loading from cache - Installing doctrine/lexer (1.2.0): Loading from cache - Installing doctrine/annotations (1.10.2): Loading from cache - Installing symfony/asset (v5.0.8): Loading from cache - Installing symfony/webpack-encore-bundle (v1.7.3): Loading from cache Writing lock file Generating autoload files Symfony operations: 6 recipes (872bda013e9fd67f074fc6bf021f1c62) - Configuring symfony/maker-bundle (>=1.0): From github.com/symfony/recipes:master - Configuring symfony/twig-bundle (>=5.0): From github.com/symfony/recipes:master - Configuring twig/extra-bundle (>=v3.0.3): From auto-generated recipe - Configuring symfony/web-profiler-bundle (>=3.3): From github.com/symfony/recipes:master - Configuring doctrine/annotations (>=1.0): From github.com/symfony/recipes:master - Configuring symfony/webpack-encore-bundle (>=1.6): From github.com/symfony/recipes:master Executing script cache:clear [OK] Executing script assets:install public [OK] Some files may have been created or updated to configure your new packages. Please review, edit and commit them: these files are yours. /var/www/apps.local.subdirphp bin/console make:controller Choose a name for your controller class (e.g. VictoriousPuppyController): > Default created: src/Controller/DefaultController.php created: templates/default/index.html.twig Success! Next: Open your new controller class and add some pages!
Super! Damit sollte ich nun eine Route /default
eingerichtet haben, mit einer dazugehörigen rudimentären Seite,
deren template in templates/default/index.html.twig
liegt. Das wollen wir uns später
noch etwas näher ansehen, erst einmal müssen wir aber nginx anpassen, damit wir überhaupt etwas sehen.
Die nginx-Konfigurationsdatei muss um einen location
Block erweitert werden:
server { server_name apps.local; root /var/www/apps.local; index index.html index.php; location / { try_files $uri /index.html; } set $subdir_root /var/www/apps.local.subdir/public; location /subdir { alias $subdir_root; try_files $uri $uri/ /index.php$is_args$args; } # return 404 for all other php files. location ~ \.php$ { return 404; } error_log /var/log/nginx/apps.local_error.log; access_log /var/log/nginx/apps.local_access.log; }
Achtung: Das ist noch nicht die finale Version. Wichtig ist, dass wir in dem location
Block ein
alias benutzen, nicht noch einmal
root, denn sonst würde eine URL wie
http://apps.local.subdir/subdir/index.php
zu dem Pfad /var/www/apps.local.subdir/public/subdir/index.php
aufgelöst werden.
Wenn wir nun die "subdir" Instanz URL http://apps.local/subdir/ im browser benutzen, bekommen wir allerdings eine Fehlermeldung:
404 Not Found nginx/1.16.1
Und in dem logfile /var/log/nginx/apps.local_access.log
finden wir den Eintrag:
127.0.0.1 - - [19/May/2020:10:43:13 +0200] "GET /subdir HTTP/1.1" 404 153 "-" ...
Das liegt daran, dass der Block mit dem "404" Fehler am Ende der Konfiguration greift. Ohne den würde die statische Seite der "default" Instanz ausgegeben werden. Der "404" Block greift für alle php Dateien, das ist aber nicht unsere Intention. Wir wollen PHP an php-fpm übergeben, haben jedoch php-fpm noch nicht eingebunden und müssen die Konfiguration erst einmal nachziehen. Die internal Direktive lassen wir vorerst weg, da sie uns beim debugging stört. In der finalen Version wird sie jedoch wieder enthalten sein.
server { server_name apps.local; root /var/www/apps.local; index index.html index.php; location / { try_files $uri /index.html; } set $subdir_root /var/www/apps.local.subdir/public; location /subdir { alias $subdir_root; try_files $uri $uri/ /index.php$is_args$args; } location ~ ^/index\.php(/|$) { fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; fastcgi_param APP_ENV dev; # Prevents URIs that include the front controller. This will 404: # http://domain.tld/index.php/some-path # Remove the internal directive to allow URIs like this # internal; } # return 404 for all other php files. location ~ \.php$ { return 404; } error_log /var/log/nginx/apps.local_error.log; access_log /var/log/nginx/apps.local_access.log; }
Wenn wir nun die Seite http://apps.local/subdir/ erneut aufrufen, erhalten wir wieder einen 404-Fehler, diesmal sieht er jedoch etwas anders aus. Im browser steht die Fehlermeldung:
File not found.
In /var/log/nginx/apps.local_access.log
steht wiederum:
127.0.0.1 - - [19/May/2020:11:24:52 +0200] "GET /subdir HTTP/1.1" 404 27 "-" ...
Jedoch in /var/log/nginx/apps.local_error.log
finden wir nun die php-fpm Fehlermeldung:
2020/05/19 11:25:22 [error] 4728#4728: *1 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 127.0.0.1, server: apps.local, request: "GET /subdir HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "apps.local"
Der Fehler liegt nun darin, dass der Parameter SCRIPT_FILENAME
auf $realpath_root$fastcgi_script_name
zeigt. Dabei löst $realpath_root
auf das Verzeichnis auf, das als root
angegeben wird. In der
Dokumentation liest sich das für mich so,
als müsste das auch innerhalb einer eine Alias-Konfiguration funktionieren. Das tut es aber nicht wie erwartet, es ist hier notwendig,
die Variable $request_filename zu benutzen.
Die Konfiguration für php wird in dem location
Block
verschachtelt, so dass mehrere "subdir" Instanzen unterschiedlich konfiguriert werden können. Der ursprüngliche location
Block für php wird nun nicht mehr gebraucht und entfernt. Er könnte jedoch auch erhalten bleiben, wenn man in der "default"
Instanz mit php arbeiten möchte.
server { server_name apps.local; root /var/www/apps.local; index index.html index.php; location / { try_files $uri /index.html; } set $subdir_root /var/www/apps.local.subdir/public; location /subdir { alias $subdir_root; try_files $uri $uri/ /index.php$is_args$args; location ~ \.php { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param DOCUMENT_ROOT $subdir_root; fastcgi_split_path_info ^(.+\.php)(/.*)$; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; internal; } } # return 404 for all other php files. location ~ \.php$ { return 404; } error_log /var/log/nginx/apps.local_error.log; access_log /var/log/nginx/apps.local_access.log; }
Wenn wir nun die "subdir" Instanz unter http://apps.local/subdir/ aufrufen, bekommen wir erstmals unsere Symfony-Installation zu sehen, wenn auch noch mit Problemen.
Der Web Profiler kann offensichtlich nicht laden. Wenn man den Request mit den debugging tools des browsers untersucht, sieht man auch, dass die Seite eigentlich einen "404" Fehler wirft, dass ist dann jedoch schon innerhalb der symfony app.
Auch die schon eingerichte Route http://apps.local/subdir/default
funktioniert nicht, hier sehen wir allerdings wieder den nginx-Fehler vom Anfang. Hingegen
http://apps.local/subdir/index.php/default funktioniert,
solange wir die internal
Direktive nicht eingeschaltet haben.
Innerhalb des try_files
Blocks kann offenbar die index.php
nicht mehr ermittelt werden, wenn
sie nicht explizit angegeben wird. Wenn wir das Verzeichnis public/default
anlegen und darin eine Datei
index.php
anlegen, wird diese ausgegeben (bitte nach einem Test wieder löschen)!
Um das Problem aufzulösen, müssen wir für den try_files
Block eine
rewrite Direktive einfügen.
Den folgenden rewrite könnten wir direkt in dem location
Block einbauen, ich benutze jedoch eine
named location, um bei dieser
Konfiguration übersichtlich zu bleiben, falls mehrere rewrites hinzukommen. Beachte, dass "named locations" nicht verschachtelt
werden können und auf "server" Ebene eingestellt werden müssen.
Gleichzeitig aktivieren wir die internal
Direktive:
server { server_name apps.local; root /var/www/apps.local; index index.html index.php; location / { try_files $uri /index.html; } set $subdir_root /var/www/apps.local.subdir/public; location /subdir { alias $subdir_root; try_files $uri $uri/ @subdir; location ~ \.php { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param DOCUMENT_ROOT $subdir_root; fastcgi_split_path_info ^(.+\.php)(/.*)$; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; internal; } } location @subdir { rewrite /subdir/(.*)$ /subdir/index.php?/$1 last; } # return 404 for all other php files. location ~ \.php$ { return 404; } error_log /var/log/nginx/apps.local_error.log; access_log /var/log/nginx/apps.local_access.log; }
Spoiler: Dies ist die finale Version.
Mit dieser Konfiguration können wir nun endlich unseren Controller sehen und auch der Web Profiler wird erfolgreich geladen!
Aber wir sind immer noch nicht ganz fertig, ich möchte sicherstellen, dass auch alle CSS und javascript assets ordentlich geladen werden.
Dazu passe ich das "base" template templates/base.html.twig
an und baue meine CSS styles in assets/css/app.css
ein:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %} {{ encore_entry_link_tags('app') }} {% endblock %} </head> <body> {% block body %}{% endblock %} {% block javascripts %} {{ encore_entry_script_tags('app') }} {% endblock %} </body> </html>
html { background-image: linear-gradient(160deg, #efedf8 0%, #1fdbc8 51%, #2ce8b5 75%); height: 100%; margin: 0; }
Dann müssen wir noch die Webpack-Konfiguration entsprechend diesen FAQ and Common Issues anpassen und die assets erzeugen:
... // public path used by the web server to access the output path .setPublicPath('/subdir/build') // only needed for CDN's or sub-directory deploy .setManifestKeyPrefix('build/') ...
/var/www/apps.local.subdirnpm install ... /var/www/apps.local.subdiryarn encore dev yarn run v1.22.4 $ /var/www/apps.local.subdir/node_modules/.bin/encore dev Running webpack ... DONE Compiled successfully in 476ms I 3 files written to public/build Entrypoint app = runtime.js app.css app.js Done in 1.35s.
Nun zeigt die Seite auch meine CSS styles an. Auch über die developer tools im browser sehen wir, dass alle assets richtig geladen werden. Ebenfalls die "default" Instanz funktioniert noch wie erwartet.
Zusammenfassung
Ich finde, dass das deutlich komplizierter einzustellen ist, als ich erwartet hätte.