---
title: "Post-Mortem — OpenResty 500 : module Lua CrowdSec"
description: "Pourquoi des scans Internet déclenchaient des HTTP 500 sur arleo.eu : module Lua CrowdSec introuvable, cause racine, correctif durable et runbook."
url: "https://www.arleo.eu/posts/post-mortem-openresty-crowdsec-lua-500/"
language: "fr"
datePublished: "2026-08-17T11:34:51Z"
dateModified: "2026-08-17T11:34:51Z"
tags: ["openresty","crowdsec","incident","security","homelab"]
categories: ["incidents"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Post-Mortem — OpenResty 500 : module Lua CrowdSec
Pourquoi des scans Internet déclenchaient des HTTP 500 sur arleo.eu : module Lua CrowdSec introuvable, cause racine, correctif durable et runbook.



{{< admonition type=info title="Ce qui s'est passé" open=false >}}
- **Date :** 16–17 août 2026
- **Sévérité :** P1
- **Statut :** Résolu
- **Impact :** HTTP 500 sur l'origine, y compris sur du trafic légitime et le endpoint de monitoring `/ping`
{{< /admonition >}}

## En bref

Deux rafales de scan Internet ont révélé un défaut latent dans la chaîne OpenResty/CrowdSec. Les scanners n'ont pas « fait tomber » OpenResty par leur volume : la cadence restait faible pour nginx. Ils ont surtout déclenché un chemin d'exécution qui appelait un module Lua custom impossible à retrouver sur des workers OpenResty fraîchement chargés.

La cause racine était ce chargement dans `access_by_lua` :

```lua
require("crowdsec.access")
```

alors que le `lua_package_path` actif ne contenait pas le répertoire où vivait le module custom `crowdsec/access.lua`.

Le résultat : **un worker qui devait charger ce module à froid renvoyait HTTP 500 avant même d'arriver à l'authentification ou au contenu Hugo**.

## Ce qui a déclenché l'incident

Deux sources ont été observées pendant l'enquête :

- `34.75.198.72` — première vague vers 08:27 UTC, puis nouvelle série de probes vers 08:35 ;
- `34.169.50.247` — nouvelle rafale vers 09:14 UTC, avec environ 95 requêtes en 12 secondes dont 94 en HTTP 500.

Les scans visaient notamment des chemins de configuration, de secrets et d'outils IA/MCP :

```text
/.env
/.claude.json
/.claude/settings.json
/.mcp.json
/.codex/config.toml
/.cursor/mcp.json
/config/secrets.yml
/config/master.key
/application.yml
/appsettings.json
/google-credentials.json
/service-account.json
/debug/pprof/
/metrics
/@fs/proc/self/environ?import&raw??
```

Une partie du trafic utilisait le User-Agent :

```text
Mozilla/5.0 (compatible; GrokBot/1.0; +https://x.ai/grokbot)
```

Ce User-Agent est trivialement falsifiable : **cela ne constitue pas une attribution à xAI**.

## Timeline condensée

| Heure UTC | Événement |
|---|---|
| ~08:27 | Première rafale depuis `34.75.198.72` : configs IA/MCP, secrets et fichiers sensibles |
| ~08:28 | Des requêtes malveillantes et légitimes commencent à recevoir HTTP 500 |
| ~08:35 | Deuxième vague de probes PHP/webshell depuis la même source |
| 09:08 | CrowdSec bannit finalement `34.75.198.72` via `crowdsecurity/http-crawl-non_statics` |
| 09:14:30–09:14:42 | `34.169.50.247` envoie ~95 requêtes ; 94 retournent 500 |
| 09:16 | Les sondes BetterStack sur `/ping` reçoivent elles aussi 500 depuis plusieurs régions |
| 17 août, investigation | Les logs OpenResty révèlent `module 'crowdsec.access' not found` |
| 13:20 locale | Premier correctif du `lua_package_path`, reload et disparition des 500 |
| 13:23 locale | Correctif durable remplacé par un symlink hors du fichier réécrit par le paquet |
| 11:31 UTC | BetterStack confirme `/ping` de nouveau UP |

## Signature exacte de la panne

Dans `/var/log/nginx/www.arleo.eu.error.log` :

```text
lua entry thread aborted: runtime error:
access_by_lua(snippets/crowdsec_access.conf:26):3:
module 'crowdsec.access' not found:
    no field package.preload['crowdsec.access']
    no file '/usr/local/openresty/nginx//../lualib/plugins/crowdsec/crowdsec/access.lua'
    ...
```

Le bloc concerné était simple :

```lua
access_by_lua_block {
    if ngx.var.lan == "1" then return end
    require("crowdsec.access")
}
```

Le `require()` n'était pas encapsulé et son échec faisait donc échouer la requête entière.

Le `lua_package_path` chargé par le bouncer officiel était :

```nginx
lua_package_path '$prefix/../lualib/plugins/crowdsec/?.lua;;';
```

alors que les modules custom vivaient sous :

```text
/usr/local/openresty/nginx/conf/lua/crowdsec/
```

`access.lua`, `mitigation.lua`, `sync.lua`, `heuristics.lua`, `tarpit.lua` et `waf_refs.lua` existaient bien : **c'est leur chemin de recherche qui manquait**.

## Pourquoi le bug semblait intermittent

`require()` met en cache un module uniquement lorsqu'il a été chargé avec succès. Avec `lua_code_cache on`, un worker qui avait déjà chargé `crowdsec.access` pouvait continuer à fonctionner normalement.

En revanche, tout worker neuf ou recyclé qui devait refaire le `require()` utilisait le mauvais `lua_package_path` et tombait en 500.

Cela explique pourquoi :

- des pages pouvaient sembler normales derrière le cache Cloudflare ;
- `/ping`, en `no-store`, continuait à atteindre l'origine et exposait le bug ;
- une rafale de scan pouvait faire apparaître brutalement plusieurs workers défectueux ;
- le problème ressemblait à une surcharge alors que le volume lui-même n'était pas anormal pour OpenResty.

## Faux diagnostic initial

Au début, plusieurs pistes semblaient crédibles :

- contenu Hugo récemment modifié ;
- vhost apex `arleo.eu` cassé alors que `www.arleo.eu` fonctionnait ;
- surcharge OpenResty/CrowdSec sous scan ;
- problème d'authentification du endpoint `/ping`.

Le MCP Hugo a permis d'éliminer immédiatement le contenu : build sain, publication cohérente, pages publiques présentes.

Le point décisif a été de lire **le log OpenResty local complet**, pas seulement les métriques HTTP agrégées : le même traceback Lua apparaissait pour chaque 500.

## Correctif appliqué

### Premier correctif : ajouter le chemin manquant

Le correctif immédiat a été :

```nginx
lua_package_path '$prefix/../lualib/plugins/crowdsec/?.lua;$prefix/lua/?.lua;;';
```

Puis :

```bash
sudo openresty -t
sudo openresty -s reload
```

Après reload : nouveaux workers, plus de `module 'crowdsec.access' not found`, plus de 500.

### Correctif durable : ne pas modifier le fichier réécrit par le paquet

Le fichier `crowdsec_openresty.conf` est réécrit par les mises à jour du bouncer CrowdSec et n'est pas protégé comme un conffile dpkg. Modifier directement son `lua_package_path` ferait donc revenir le bug à une future mise à jour.

Le fichier a été remis dans son état stock et un lien symbolique a été créé :

```text
/usr/local/openresty/lualib/plugins/crowdsec/crowdsec
    -> /usr/local/openresty/nginx/conf/lua/crowdsec
```

Ainsi le chemin officiel :

```text
.../plugins/crowdsec/?.lua
```

résout naturellement :

```text
require("crowdsec.access")
→ .../plugins/crowdsec/crowdsec/access.lua
```

sans modifier le fichier géré par le paquet.

## Validation après correction

Les contrôles effectués après reload :

- `openresty -t` : OK ;
- nouveaux workers OpenResty démarrés proprement ;
- 15 requêtes séquentielles sur `/ping` : aucune 500 ;
- 30 requêtes concurrentes sur `/ping` : aucune 500 ;
- aucune nouvelle occurrence de `module 'crowdsec.access' not found` ;
- aucune 500 dans l'access log après le correctif ;
- `/` et les pages Hugo : 200 ;
- apex `arleo.eu` : redirection 301 vers `www` ;
- BetterStack : monitor `/ping` repassé **UP** après le correctif.

Le `401` observé sans credentials sur `/ping` est normal : ce endpoint est protégé par Basic Auth. BetterStack possédait déjà la bonne configuration d'authentification ; auparavant le crash `access_by_lua` se produisait **avant** `auth_basic`.

## Si cela se reproduit

Ne pas commencer par rollbacker Hugo.

### 1. Vérifier immédiatement la signature Lua

```bash
sudo grep -n "crowdsec.access\|lua entry thread aborted" \
  /var/log/nginx/www.arleo.eu.error.log | tail -50
```

Si on voit :

```text
module 'crowdsec.access' not found
```

la cause est probablement la même.

### 2. Vérifier que le module est résolvable

```bash
ls -la /usr/local/openresty/lualib/plugins/crowdsec/crowdsec
ls -la /usr/local/openresty/nginx/conf/lua/crowdsec/access.lua
```

Le premier chemin doit être un symlink vers le dossier custom.

### 3. Vérifier la configuration réellement chargée

```bash
sudo openresty -T 2>&1 | grep -n "lua_package_path\|crowdsec_access"
```

### 4. Tester avant reload

```bash
sudo openresty -t
```

Attention : `openresty -t` vérifie la syntaxe mais **n'exécute pas le `require()` de la phase request**. Un test HTTP réel est donc obligatoire.

### 5. Tester le chemin d'exécution réel

```bash
curl -sk -o /dev/null -w '%{http_code}\n' https://www.arleo.eu/ping
```

Sans auth, un `401` propre est préférable à un `500`. Avec les credentials du monitor, le résultat attendu est `200`.

### 6. Vérifier l'absence de nouvelles 500

```bash
sudo tail -f /var/log/nginx/www.arleo.eu.error.log
```

puis lancer plusieurs requêtes concurrentes.

## Conclusion

Les scanners `34.75.198.72` et `34.169.50.247` ont été les **déclencheurs observés**, mais pas la cause racine. Une centaine de requêtes en quelques secondes ne devrait pas mettre OpenResty à terre.

La véritable défaillance était un **module Lua custom CrowdSec présent sur disque mais absent du chemin de recherche des workers OpenResty**. La sécurité était elle-même devenue un point de panne global : une erreur de `require()` dans `access_by_lua` transformait n'importe quelle requête en HTTP 500 avant le reste de la chaîne nginx.

La leçon principale est simple : **quand une panne semble corrélée à une attaque, vérifier que l'attaque ne fait pas seulement apparaître un défaut latent de notre propre couche de défense.**

---

![CrowdSec, OpenResty et Lua face au trafic malveillant et aux erreurs HTTP 500](/images/logos/crowdsec-openresty-appsec.webp)

## Liens internes utiles

- [CrowdSec AppSec + OpenResty : WAF moderne sans ModSecurity](/posts/crowdsec-appsec-openresty/)
- [CrowdSec avec Vector : filtrer le bruit, garder les bans](/posts/crowdsec-vector-pipeline/)
- [Postmortem CrowdSec : faux positif sur Sonarr/Radarr](/posts/postmortem-crowdsec-appsec-false-positive-sonarr/)
- [Post-Mortem — Incident 522 / WAN Failover](/posts/post-mortem-522-wan-failover/)

## Tags

- openresty
- crowdsec
- incident
- security
- homelab

## Categories

- incidents
