Apache
Voici la configuration de reverse proxy réellement utilisée par Aetheus en production, avec les jetons propres au déploiement remplacés par des valeurs d'exemple. En cas de doute sur le proxy à choisir, prenez celui-ci.
Activer les modules
sudo a2enmod ssl proxy proxy_http proxy_wstunnel rewrite headers
sudo systemctl restart apache2
proxy_wstunnel n'est pas facultatif : sans lui, l'hôte API ne peut pas passer en WebSocket, et la sortie des exécutions en direct cesse de se mettre à jour alors que tout le reste semble fonctionner.
Obtenir un certificat
Un seul certificat couvrant les deux noms d'hôte garde la configuration simple :
sudo certbot certonly --apache \
-d app.example.com \
-d api.example.com
Hôte applicatif
Il sert le frontend. Que les fichiers viennent d'un conteneur ou d'un wwwroot publié, les deux règles qui comptent sont les mêmes : ne jamais mettre en cache les fichiers d'amorçage, et laisser fonctionner le routage côté client.
<VirtualHost *:443>
ServerName app.example.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
ProxyPreserveHost On
ProxyRequests Off
RequestHeader set X-Forwarded-Proto "https"
# Une application Blazor WebAssembly s'amorce depuis index.html et _framework/. Les mettre en
# cache fait qu'un navigateur continue d'exécuter la version précédente après une mise à jour.
<LocationMatch "^/(index\.html|service-worker(\.published)?\.js|_framework/|_content/Radzen\.Blazor/)">
Header always set Cache-Control "no-cache, no-store, must-revalidate"
Header always set Pragma "no-cache"
Header always set Expires "0"
</LocationMatch>
ProxyPass / http://127.0.0.1:10025/
ProxyPassReverse / http://127.0.0.1:10025/
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
ErrorLog ${APACHE_LOG_DIR}/app.example.com_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/app.example.com_ssl_access.log combined
</VirtualHost>
Servir les fichiers directement plutôt que les relayer
Si vous avez publié le frontend sur disque plutôt que d'exécuter le conteneur, remplacez les deux lignes ProxyPass par une racine documentaire et un repli pour que les chemins inconnus atteignent index.html :
DocumentRoot /var/www/aetheus-app
<Directory /var/www/aetheus-app>
Require all granted
Options -Indexes
FallbackResource /index.html
</Directory>
Hôte API
Il sert le backend. Le bloc de réécriture est ce qui transforme une demande de mise à niveau en connexion WebSocket relayée ; il doit précéder ProxyPass.
<VirtualHost *:443>
ServerName api.example.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
ProxyPreserveHost On
ProxyRequests Off
RequestHeader set X-Forwarded-Proto "https"
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) ws://127.0.0.1:10026/$1 [P,L]
ProxyPass / http://127.0.0.1:10026/
ProxyPassReverse / http://127.0.0.1:10026/
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
ErrorLog ${APACHE_LOG_DIR}/api.example.com_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/api.example.com_ssl_access.log combined
</VirtualHost>
Rediriger le HTTP en clair
Un hôte virtuel par nom, sur le port 80, redirigeant vers HTTPS :
<VirtualHost *:80>
ServerName app.example.com
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<VirtualHost *:80>
ServerName api.example.com
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Appliquer
sudo apachectl configtest
sudo systemctl reload apache2
Lancez toujours configtest d'abord : un rechargement avec une configuration cassée coupe le site au lieu d'être ignoré.
Vérifier
# L'API répond, et sa base est joignable.
curl -sf https://api.example.com/health/ready
# Le frontend est servi et n'est pas mis en cache.
curl -sI https://app.example.com/ | grep -i cache-control
Ouvrez ensuite l'hôte applicatif dans un navigateur et connectez-vous. Si la page se charge mais que chaque appel échoue, les deux réglages à vérifier sont le ApiBaseUrl du frontend et le Cors__Origins__0 du backend.
Pannes courantes
| Symptôme | Cause habituelle |
|---|---|
| L'application se charge, chaque appel d'API échoue avec une erreur CORS. | Cors__Origins__0 ne correspond pas exactement à l'hôte applicatif, schéma compris. |
| L'application se charge mais les appels partent vers la mauvaise adresse. | ApiBaseUrl dans le appsettings.json du frontend a été écrasé par un redéploiement. |
| Tout fonctionne mais la sortie en direct ne se rafraîchit jamais. | proxy_wstunnel n'est pas activé, ou le bloc de réécriture manque sur l'hôte API. |
| Un rafraîchissement sur un lien profond renvoie 404. | Pas de repli vers index.html quand les fichiers sont servis directement. |
| Le navigateur exécute encore la version précédente après une mise à jour. | Les règles no-store sur index.html et _framework/ manquent. |