- DevOps
- Linux
- Spring Boot
- Nginx
Déployer Spring Boot sur un VPS Linux : systemd, Nginx, TLS
La configuration de production que j'utilise pour exposer une API Spring Boot derrière Nginx, avec redémarrage automatique, TLS renouvelé et journaux exploitables.
Un java -jar lancé dans un screen tient jusqu'au premier redémarrage du serveur. Pour une API qui porte une application mobile en production, il faut un service supervisé. Voici la configuration que je déploie systématiquement.
Un utilisateur dédié, sans shell
Première règle : l'application ne tourne jamais en root.
sudo useradd --system --no-create-home --shell /usr/sbin/nologin api
sudo install -d -o api -g api /opt/api /var/log/apiSi l'application est compromise, l'attaquant hérite d'un compte sans shell, sans home et sans droits d'écriture ailleurs que dans son répertoire de logs.
L'unité systemd
[Unit]
Description=API Spring Boot
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=api
Group=api
WorkingDirectory=/opt/api
EnvironmentFile=/etc/api/api.env
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=70 -jar /opt/api/app.jar
SuccessExitStatus=143
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/log/api
[Install]
WantedBy=multi-user.targetTrois lignes méritent une explication :
SuccessExitStatus=143: un arrêt propre viaSIGTERMrenvoie 143. Sans cette ligne, systemd considère chaque arrêt normal comme un échec.MaxRAMPercentage=70: la JVM par défaut ignore la mémoire réellement disponible sur un petit VPS et se fait tuer par l'OOM killer. On lui fixe une borne relative.EnvironmentFile: les secrets vivent dans un fichier enchmod 600, pas dans l'unité systemd, qui est lisible par tous.
Nginx en terminaison TLS
L'application écoute uniquement sur la boucle locale ; Nginx est seul exposé.
upstream api_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.exemple.com;
ssl_certificate /etc/letsencrypt/live/api.exemple.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.exemple.com/privkey.pem;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Le proxy_set_header Connection "" combiné à keepalive dans l'upstream évite de rouvrir une connexion TCP à chaque requête : gain net sur une API appelée en boucle par une application mobile.
Le cas des WebSockets
Pour un suivi GPS temps réel, la même configuration ne suffit pas : il faut autoriser la montée en version du protocole.
location /ws/ {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}Sans le proxy_read_timeout allongé, Nginx coupe la connexion au bout de soixante secondes d'inactivité, ce qui se traduit côté mobile par des reconnexions permanentes et une batterie qui fond.
Le renouvellement TLS qui n'oublie pas Nginx
certbot renew renouvelle le certificat mais ne recharge pas Nginx. Le hook de déploiement s'en charge :
echo -e '#!/bin/sh\nsystemctl reload nginx' \
| sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shUn certificat renouvelé mais jamais rechargé expire en production exactement comme s'il n'avait pas été renouvelé. C'est l'incident le plus fréquent que j'ai eu à diagnostiquer.
Vérifier plutôt qu'espérer
systemctl status api --no-pager
journalctl -u api -n 50 --no-pager
sudo nginx -t && systemctl reload nginx
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://api.exemple.com/healthUn endpoint /health exposé par Spring Boot Actuator, interrogé toutes les minutes depuis l'extérieur, détecte une panne avant que les utilisateurs ne la signalent.
Articles liés
Intégrer les API de paiement des agrégateurs sans se faire piéger
Mobile Money, cartes bancaires, agrégateurs multiples : le modèle unifié, l'idempotence et la réconciliation qui évitent les doubles débits en production.
- Paiement
- FinTech
- Architecture
- API
Ma méthode pour un front-end Angular qui reste maintenable
Découpage par domaine, composants standalone, signaux contre RxJS, intercepteurs et détection de changement : les décisions que je prends au démarrage d'un projet Angular.
- Angular
- TypeScript
- Architecture
- Frontend