Saltar al contenido

Seguridad

Prácticas y roadmap de mejora

Audito y opero 14 prácticas de seguridad en producción agrupadas por categoría NIST-CSF, con 16 ítems en el roadmap de mejora que publico abiertamente. Cada métrica verificada enlaza a su fuente; las estimadas van en cursiva con atribución.

Sección A — Prácticas (14)

Network defense

6 prácticas

fail2ban with kernel-level ban sets: O(1) drop via named addr-set, recidive horizon 90 days

showcase-evidence-onlymedium

I run fail2ban with kernel-level ban sets via nftables so the data path stays O(1) even at 10k+ offenders. My docs cover a 2-jail core pattern (sshd + recidive) plus a 6-jail mail-system extension; in production I extend this to a 5-jail stack covering transport, mail, and trusted-SSH ranges. The point is layered defense: fail2ban reacts to log evidence, the kernel rejects further packets.

Stack

  • fail2ban 1.x
  • nftables (kernel.x)
  • systemd unit: fail2ban.service
  • iptables (wg0 PostUp/PostDown only)
  • Debian defaults jail stack

Métricas verificadas

  • 2-jail fail2ban core pattern (sshd + recidive) is documented as primary ban mechanism

    loust-pro-monorepo/docs/security/DEFENSE-IN-DEPTH.md:357-365

  • 6 custom fail2ban jails for mail system (postfix, dovecot, recidive, etc.) with explicit bantime/findtime

    loust-pro-monorepo/docs/mail-system/SECURITY.md:62-148

  • fail2ban-as-IDS pattern noted in defense-in-depth narrative (no Suricata)

    loust-pro-monorepo/docs/security/DEFENSE-IN-DEPTH.md:339-365

Métricas estimadas

  • Production stack runs 5 custom jails: hardened SSH, edge transport, trusted-SSH allowlist, stricter SSH, Debian defaults
  • Named kernel ban sets (sshd, recidive, nginx 404s, postfix) reject traffic without per-packet log lookup
  • Recidive ban horizon: 90 days bantime after a 7-day findtime window — offenders that reappear stay out
  • sshd hardening: 3 attempts in 15 minutes triggers a 7-day ban
  • Mail jails: 4 attempts in 15 minutes triggers a 48-hour ban
  • trusted-ssh jail ignores the VPN mesh and 14 residential MX ranges to avoid self-lockout
  • Edge-transport jails cover QUIC, Hysteria, and gost listeners — see the transport entry for the layered rationale

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: commit the firewall table source so reviewers can diff the live config against docs
  • Next iteration: surface recidive-ban telemetry (e.g. '14 IPs banned in 24h') into the operational dashboard
  • Next iteration: integrate with the lzt-watchdog family so ban storms trigger an alert, not just a log entry
fail2bannftablesrecidive-bangeo-blockcredential-stuffingshowcase-evidence

Zero-Trust aligned to NIST SP 800-207: 6 logical components, 4 enforcement layers

verified-docsmedium

I decompose the architecture into the 6 logical components NIST defines (PE, PA, PEP, EIG, MSG, SAG) and I enforce decisions at 4 layers — kernel firewall (L4), nginx (L7), Next.js route groups (L7), and Postgres GRANTs (L8). The point is that no single layer is the policy authority; each PEP verifies the prior layer's claim. I documented the mapping to my own services; runtime PE/PA sources live in the tenant repos.

Stack

  • NIST SP 800-207 Zero Trust Architecture (reference)
  • Cloudflare Universal SSL (EIG tier)
  • nftables + fail2ban (network PEP)
  • nginx vhosts with per-tenant SSL termination (transport PEP)
  • Next.js proxy.ts + route groups (application PEP)
  • Postgres GRANT per role + per tenant (data PEP)

Métricas verificadas

  • 6 logical NIST SP 800-207 components (PE/PA/PEP/EIG/MSG/SAG) mapped to monorepo services

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:2807-2834

  • Defense-in-depth narrative: PEPs at L4 fail2ban + L7 nginx + L7 Next.js + L8 Postgres

    loust-pro-monorepo/docs/infrastructure/ADVANCED-NETWORK-OPTIMIZATION.md:329-345

Deltas de mejora

  • Next iteration: instrument the PEP layers with deny-decision telemetry so I can see failed attempts per layer
  • Next iteration: add a policy dry-run mode that evaluates requests against PA rules without enforcing, for regression testing
  • Next iteration: extract the cross-layer mapping into a machine-readable policy file so changes are auditable
zero-trustnist-800-207defense-in-depthpolicy-enginepolicy-enforcement-point

SSH bound to WireGuard: sshd listens on the mesh only, Ed25519-only, password off

verified-docshigh

I bind sshd to the WireGuard interface address only — the daemon is invisible to the public internet. Host key algorithms are restricted to Ed25519, password authentication is off, and a single failed key exchange counts against the 3-attempt budget. The principle is that any service not exposed to a hostile network has a strictly smaller attack surface; the VPN mesh is the network, and the mesh is mine.

Stack

  • OpenSSH 9.x with ListenAddress on the mesh interface
  • WireGuard wg0 interface
  • Ed25519 host keys only
  • MaxAuthTries
  • systemd unit sshd.service

Métricas verificadas

  • ListenAddress 10.8.0.1 binds sshd to WireGuard interface only (no public exposure)

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:447-482

  • PasswordAuthentication no

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:447-482

  • HostKeyAlgorithms restricted to ssh-ed25519

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:447-482

  • MaxAuthTries 3

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:447-482

sshssh-hardeningwireguarded25519vpn-boundlisten-address

Kernel hardening: BBR congestion control, 12 named sysctl keys, SYN cookies, ICMP stealth

verified-docshigh

I tune the Linux kernel for both performance and posture. BBR congestion control keeps throughput stable across lossy links; SYN cookies absorb connection floods; reverse-path filtering drops spoofed packets; and the ICMP echo is fully suppressed so the host does not advertise itself. Twelve named sysctl keys are documented; the cgroup-v2 boundary adds the kernel-pointer and dmesg restrictions that protect against local info leaks.

Stack

  • Linux kernel sysctl (/etc/sysctl.d/99-loust-hardening.conf)
  • BBR congestion control (net.ipv4.tcp_congestion_control)
  • SYN cookies (net.ipv4.tcp_syncookies)
  • Reverse-path filtering (net.ipv4.conf.all.rp_filter)
  • ICMP echo ignore (net.ipv4.icmp_echo_ignore_all)
  • TCP Fast Open (net.ipv4.tcp_fastopen)

Métricas verificadas

  • 12 named sysctl keys documented with explicit values (BBR, rmem_max=16777216, syncookies=1, fastopen=3, rp_filter=1, icmp_echo_ignore_all=1)

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:1604-1713

Métricas estimadas

  • Production VPS /etc/sysctl.d/99-loust-hardening.conf contains the full 12+ key set
  • kptr_restrict=2, dmesg_restrict=1, unprivileged_bpf_disabled=2 — referenced in cgroup v2 context

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: commit the sysctl file so the live tuning is auditable in code review
  • Next iteration: extend the documented set from 12 keys to the full production tuning, with rationale per key
  • Next iteration: add a boot-time validator that diffs the running sysctls against the committed baseline
sysctlkernel-hardeningbbrsyncookiesrp-filtericmp-stealthtcp-fastopen

WireGuard 3-peer mesh with Curve25519 keys rotated every 6 months

verified-docshigh

My production mesh has 3 named peers — desktop, mobile, and a portable laptop — and I rotate the Curve25519 keys every 6 months. The mesh is the only path that touches production credentials, so the rotation cadence is the operational floor on key lifetime. The earlier narrative claimed 5 peers; the audit found 3, which I publish as the truth.

Stack

  • WireGuard (kernel module)
  • Curve25519 key exchange
  • iptables MASQUERADE for VPN-egress
  • systemd unit wg-quick@wg0.service
  • 6-month key rotation policy

Métricas verificadas

  • 3 named peers: PC (10.8.0.2), Android (10.8.0.3), Laptop hermano (10.8.0.4)

    loust-pro-monorepo/docs/security/DEFENSE-IN-DEPTH.md:97-109

  • Curve25519 + 6-month rotation policy + MASQUERADE for VPN-egress

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:160-166

Deltas de mejora

  • Next iteration: commit the mesh config so rotation events are auditable in version control
  • Next iteration: surface the per-peer handshake timestamp in the operational dashboard so a stale peer is visible at a glance
  • Next iteration: write the post-rotation runbook so the 6-month cadence is one operator action
wireguardmeshcurve25519key-rotationmasqueradevpn-egress

Postgres VPN-only with mandatory SSL: pg_hba allowlist, two-role isolation, Prisma Singleton

verified-docshigh

I lock Postgres to the VPN mesh: pg_hba.conf rejects every connection outside the mesh allowlist, SSL is mandatory on every connection, and the database is reachable only through a Prisma Singleton so the application has exactly one connection path. Two roles — the application user and the admin — keep blast radius small if a credential leaks. The design is the same one I would reach for in a multi-tenant SaaS: the data plane is its own network.

Stack

  • PostgreSQL 16.x with ssl=on
  • pg_hba.conf VPN-only allowlist (10.8.0.0/24)
  • Two-role isolation (bibliotecario_loust, postgres)
  • Prisma Singleton as sole entry point
  • WireGuard mesh 10.8.0.0/24

Métricas verificadas

  • pg_hba.conf rejects all external connections (VPN-only allowlist)

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:148-152

  • ssl=on mandatory + TLS 1.2 minimum for all client connections

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:185-198

  • Two-role isolation: bibliotecario_loust (app) + postgres (admin)

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:148-152

postgrespg-hbavpn-onlyssl-mandatoryrole-isolationprisma-singleton

Identity & RBAC

1 práctica

Multi-tenant RBAC across socialspheremx + fuerzaparaseguir + loust.pro: 5 roles, NextAuth 30-day session

verified-docshigh

I run RBAC across three product surfaces with five roles enforced by a shared matrix. Each tenant gets its own Postgres database plus Redis key-prefix isolation, and I scope access at the route-group layer so a tenant never sees another tenant's data. The design is documented in my SOC2/GDPR notes; the runtime source lives in the tenant repos.

Stack

  • Next.js 14+ route groups + (authed) layouts
  • Prisma 6.x with per-tenant DATABASE_URL
  • NextAuth (session provider, 30d JWT)
  • Redis with key prefix isolation per tenant
  • bcrypt (Node 25 ABI 141) for password hashing
  • PM2 fork-mode processes per tenant (ecosystem.config.js)

Métricas verificadas

  • 5 distinct roles enforced via RBAC matrix in monorepo docs

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:148-159

  • NextAuth JWT session lifetime is 30 days

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:155-159

  • Per-tenant Postgres isolation enforced via GRANT per role per tenant

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:148-152

Deltas de mejora

  • Next iteration: consolidate the per-tenant RBAC matrix into a single source of truth so role drift is impossible
  • Next iteration: surface the role-to-route mapping in the operational dashboard so I can audit live which roles are active where
  • Next iteration: add session-revocation primitives for emergency role-down events
rbacmulti-tenantnext.jspostgres-isolationredis-prefixroute-groups

Transport

2 prácticas

WireGuard mesh + QUIC tunnel SSH fallback: 5-tier transport chain with kill-switch prevention

showcase-evidence-onlymedium

I run a WireGuard mesh as the primary transport, with a 5-tier QUIC fallback chain for cases where the kernel module is unavailable or the link is hostile. The tier ladder — quic-tunnel, Hysteria2, gost, tls-direct, raw SSH — is designed so that any one tier dying degrades gracefully into the next without taking the service offline. The tls-direct tier pins a 10-year CA to prevent MITM even on compromised paths.

Stack

  • WireGuard (kernel module wg0)
  • Hysteria2 (QUIC, congestion control brutal/BBR/cubic)
  • gost v3.2.6 (relay + client)
  • quic-tunnel (Go, my OSS repo)
  • Go crypto/tls (tls-direct tier with pinned CA)
  • OpenSSH ProxyCommand + ssh-proxy

Métricas verificadas

  • 2-tier transport fallback (QUIC↔SSH) is the documented baseline

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:1052-1077

Métricas estimadas

  • 5-tier fallback (quic-tunnel, Hysteria2, gost, tls-direct, raw SSH) lives in louzt/quic-tunnel (OSS, my repo)
  • tls-direct tier: pinned 10-year CA, named CN, SAN bound to localhost + the production edge
  • 5 unit tests covering PinnedTLSClientConfig + DialTLSProxy
  • tls-direct observed roundtrip ~1 s, 5.4 KB up / 4.6 KB down (single test point)
  • Edge-transport fail2ban jails monitor the QUIC, Hysteria, and gost listeners — see the fail2ban entry

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: extend my docs from 2-tier to the full 5-tier so the rationale is in one place
  • Next iteration: replace single-test tls-direct numbers with load-test averages across packet-loss profiles
  • Next iteration: wire edge-transport fail2ban telemetry into the operational dashboard
wireguardquichysteria2kill-switch-preventiontls-pinningssh-proxyshowcase-evidence

mTLS QUIC tunnel with 4096-bit RSA CA + ALPN pinned + systemd hardening

verified-docshigh

The QUIC tunnel that fronts my SSH fallback runs mTLS end-to-end with a self-signed 4096-bit RSA CA. I pin ALPN to the protocol version so the negotiation is unforgeable, and I run the daemon under systemd hardening directives (NoNewPrivileges, ProtectSystem strict, ProtectHome) so a kernel-level compromise does not translate to filesystem reach. The cert is 10-year because the rotation cadence is yearly and the window is wide on purpose.

Stack

  • Go crypto/tls with 4096-bit RSA CA
  • ALPN quic-tunnel/1 (pinned)
  • 10-year certificate validity
  • systemd hardening: NoNewPrivileges=true
  • systemd hardening: ProtectSystem=strict
  • systemd hardening: ProtectHome=true

Métricas verificadas

  • 4096-bit RSA CA, 10-year validity, CN=quic-tunnel-server/client

    loust-pro-monorepo/docs/infrastructure/quic-tunnel-deployment.md:60-80

  • ALPN quic-tunnel/1 (pinned)

    loust-pro-monorepo/docs/infrastructure/quic-tunnel-deployment.md:128-148

  • systemd hardening: NoNewPrivileges=true, ProtectSystem=strict, ProtectHome=true

    loust-pro-monorepo/docs/infrastructure/quic-tunnel-deployment.md:72-76

mtlsquicalpn-pinningrsa-4096systemd-hardeningnonewprivileges

Observability

1 práctica

Self-hosted Telegram bot for ops alerts: 4 severity tiers, 6-hour digest cadence, no SaaS

verified-docsmedium

I run a self-hosted Telegram bot for ops alerts with 4 severity tiers (info, warn, critical, emergency) so that the on-call experience is the same whether I am at the keyboard or away. The bot binary runs under PM2 and pulls a digest every 6 hours; a local LLM summarizes the window into a Telegram-friendly message. The pipeline is fully self-hosted — no SaaS alert vendor, no third-party data egress.

Stack

  • Telegram Bot API (long-poll)
  • Bun runtime for the microservice binary
  • PM2 process manager
  • systemd cron under /etc/cron.d/ for the 6-hour digest
  • Local LLM (MiniMax M3 + Ollama) for digest summarization
  • curl + jq for cron <-> bot API contract

Métricas verificadas

  • 4-tier severity alert pipeline (info/warn/critical/emergency) + Telegram bot

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:262-285

  • Telegram bot process is documented in PM2 runtime topology

    loust-pro-monorepo/docs/infrastructure/VPS-RUNTIME-TOPOLOGY.md:14

  • Bot port allocation documented in VPS-RUNTIME-TOPOLOGY (port 3051)

    loust-pro-monorepo/docs/infrastructure/VPS-RUNTIME-TOPOLOGY.md:40

Métricas estimadas

  • Bot binary runs under PM2, ~87 MB RSS, 0% CPU at idle — typical microservice footprint
  • Health endpoint returns 200 on readiness, with a 30 s boot-wait retry loop for the bot contract
  • Digest cron fires every 6 hours (00:00, 06:00, 12:00, 18:00 UTC)
  • Local LLM provider chain: 6 providers compressed down to 2 (MiniMax M3 + Ollama local)

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: mirror the bot source into the docs repo so reviewers can read the contract end-to-end
  • Next iteration: publish the cron template so a fresh tenant can stand up the same cadence in one commit
  • Next iteration: document the LLM provider migration path so the bot stays accurate when the local model changes
telegram-botself-hostedops-alertsbun-runtimeno-saas

Cryptographic

3 prácticas

LetsEncrypt: dedicated cert per subdomain, no multi-SAN expansion

verified-docshigh

I issue a dedicated LetsEncrypt certificate per subdomain rather than a single multi-SAN certificate. The reason is operational: certbot --expand re-validates every SAN in the cert, so if any vhost webroot drifts the whole renewal fails. Per-subdomain certs make each renewal independent — one failure does not cascade. The docs cover 9 of the production certs; the rest live in the operational inventory.

Stack

  • LetsEncrypt (EFF ACME CA)
  • certbot --webroot (HTTP-01 challenge)
  • nginx (AC-only :80 vhost with /.well-known/acme-challenge/ root /var/www/certbot)
  • Cloudflare Universal SSL (edge termination layer)
  • systemd timer (certbot auto-renewal, daily check)

Métricas verificadas

  • 9 repo-managed LetsEncrypt certs tracked in CERTIFICATE-TOPOLOGY

    loust-pro-monorepo/docs/security/CERTIFICATE-TOPOLOGY.md:14-48

  • Dedicated-per-subdomain rationale documented (certbot --expand risk on multi-SAN)

    loust-pro-monorepo/docs/security/CERTIFICATE-TOPOLOGY.md:14-48

Métricas estimadas

  • Production VPS hosts 28 dedicated cert directories under /etc/letsencrypt/live/
  • Apex cert loust.pro has 65 SANs (legacy); new subdomains bypass --expand entirely
  • showcase.loust.pro cert dedicated, expires 2026-11-04, single SAN=DNS:showcase.loust.pro
  • Renewal: per-cert independence (one renewal failure does not cascade)
  • Double TLS layer: Cloudflare Universal SSL (edge) + LetsEncrypt (nginx origin)

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: commit the cert directories so renewal failures are diffable in code review
  • Next iteration: extend the cert inventory from 9 to the full set of 28 production certs
  • Next iteration: alert when a cert is within 30 days of expiry, instead of relying on the systemd timer alone
letsencryptcertbotdedicated-certno-cert-expansionacme-webroottls-pinning

Anti-leak DNS chain: dnscrypt-proxy on clients → BIND on edge → Cloudflare DoH upstream

verified-docshigh

I run DNS as a three-stage encrypted chain so no party in the path sees queries in clear. Clients use dnscrypt-proxy over the VPN mesh; the edge resolver is BIND with the mesh as its only client; and BIND forwards to Cloudflare over DoH so even the recursive hop is encrypted. The result is that a packet capture at the hosting provider shows nothing but encrypted DNS, which is the threat model I am defending against.

Stack

  • dnscrypt-proxy v2.1.5 (client-side)
  • BIND 9.x (authoritative + recursive on VPS)
  • Cloudflare DoH (1.1.1.1 upstream, TLS-encrypted)
  • iptables PREROUTING (wg0 → mesh resolver)
  • WireGuard mesh

Métricas verificadas

  • dnscrypt-proxy v2.1.5 → BIND → Cloudflare DoH chain documented in DEFENSE-IN-DEPTH

    loust-pro-monorepo/docs/security/DEFENSE-IN-DEPTH.md:769-801

  • iptables PREROUTING forces WireGuard mesh DNS (wg0) → 10.8.0.1:53 only

    loust-pro-monorepo/docs/security/DEFENSE-IN-DEPTH.md:769-801

dns-over-httpsdnscrypt-proxybindcloudflare-dohanti-leakvpn-dns

TLS 1.2/1.3 with HSTS 2-year + 5 browser-side security headers

verified-docshigh

I terminate TLS at nginx with TLS 1.2/1.3 only, ECDHE-GCM for forward secrecy, and a 2-year HSTS so a downgrade attack is impossible after the first secure visit. Five security headers — X-Frame-Options, Referrer-Policy, and three more — close the browser-side holes. OCSP stapling is on my improvement roadmap; I publish it as a delta rather than a claim, since I have not yet wired it.

Stack

  • OpenSSL 3.x with TLS 1.2/1.3
  • nginx ssl_protocols + ssl_ciphers
  • ECDHE-GCM cipher suite (forward secrecy)
  • HSTS max-age (2 years)
  • Cloudflare Universal SSL (edge termination)

Métricas verificadas

  • ssl_protocols TLSv1.2 TLSv1.3 + ECDHE-GCM ciphers documented in nginx vhost config

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:574-593

  • HSTS max-age=63072000 (2 years) — Strict-Transport-Security header

    loust-pro-monorepo/docs/infrastructure/LOUST-INFRASTRUCTURE-COMPLETE.md:574-593

  • 5 security headers: X-Frame-Options DENY + Referrer-Policy no-referrer + 3 others

    loust-pro-monorepo/docs/security/SOC2-GDPR-COMPLIANCE.md:180-198

Deltas de mejora

  • Next iteration: wire OCSP stapling so revocation checks do not rely on client reachability
  • Next iteration: enumerate the full cipher list in the docs so reviewers can audit what is allowed
  • Next iteration: commit the nginx ssl.conf files so the live config matches the doc
tls-1.3hstsecdhesecurity-headersx-frame-optionsocsp-gap

Performance-security

1 práctica

APQ cache patterns: 90.9% hit rate, p95 12 ms, 135k-line GraphQL schema

verified-docshigh

Apollo Client v4 Automatic Persisted Queries with sha256 hashing, BatchHttpLink, CircuitBreaker, and apollo3-cache-persist. The design serves a triple purpose: it eliminates parse-and-validate overhead on the hot path, it shrinks the DoS attack surface by whitelisting persisted query hashes, and it gives the cache a stable identity that the Redis layer can deduplicate. Verified in production on a 707-Prisma-model monorepo generating 135k lines of GraphQL.

Stack

  • Apollo Client v4 (BatchHttpLink + InMemoryCache + apollo3-cache-persist)
  • Apollo Server v4 (APQ plugin with sha256 hashing)
  • Redis 7 (Lua EVAL atomic counters + persisted query cache)
  • Prisma 6.x (707 models, schema introspection)
  • Next.js 16 cache components ('use cache' + cacheLife + cacheTag + revalidateTag)
  • IndexedDB + Service Worker (browser layer)
  • Self-hosted compile-runner (replaces GitHub Actions for schema build)

Métricas verificadas

  • 135,504-line GraphQL schema from 707 Prisma entities

    loust.pro monorepo — introspected schema (separate from loust-pro-monorepo)

  • 2,089 query types and ~4,210 total operations under APQ coverage

    OSS gist 64715cb9c6ec6ffdd98c5712b8fb7bac (case study v2.1)

Métricas estimadas

  • Hit rate: 90.9% in production (loust.pro traffic)
  • p95 latency: 12 ms (vs 25 ms without APQ)
  • Payload reduction: 75% on cached queries
  • Throughput: +125% (200 → 450 req/s)
  • Auto-warmup: 2,130 queries pre-cached at boot
  • External spend: $0/month (no Apollo Studio / Hasura / Stellate)

Fuente: telemetría de producción o advisory externa.

Deltas de mejora

  • Next iteration: ship the production hit-rate and p95 numbers into the operational dashboard so the metric is auditable
  • Next iteration: lift the case study into the docs repo instead of relying on the gist link
  • Next iteration: wire APQ miss metrics into the alert pipeline so a hit-rate drop pages on-call
apqpersisted-queriesgraphqlrediscircuit-breakerdos-mitigation

Sección B — Roadmap de mejora (16)

Future work

4 ítems

IPv6 RA guard + dual-stack planning

Future work

Production is IPv4-only today. The migration to dual-stack is on the roadmap and I want to plan it before adding a single IPv6 address — Router Advertisement guard is the first thing that has to be in place before any IPv6 traffic can be safely accepted.

Evidencia actual: IPv4-only topology documented; dual-stack plan not yet authored

Acción de cierre: Author the IPv6 dual-stack migration plan + RA guard config and review before any address change

chrony + NTS authenticated time synchronization

Future work

Without authenticated time sync, log timestamps are forgeable. chrony with Network Time Security (NTS, RFC 8915) is the path forward — it adds a TLS-authenticated handshake to NTP so the time source cannot be silently substituted.

Evidencia actual: chrony/NTS not yet deployed

Acción de cierre: Deploy chrony with NTS support against trusted sources and document the verification step

Multi-VPS failover design

Future work

Production is a single VPS today. The next iteration is a second VPS in a different ASN, DNS-based failover, and Postgres replication across the two. The design has to be written before any second instance is provisioned so the failover behavior is auditable.

Evidencia actual: Single-VPS topology documented; multi-VPS design not yet authored

Acción de cierre: Author the multi-VPS design (different ASN, DNS failover, Postgres replication) before provisioning

TOTP / WebAuthn for admin endpoints

Future work

NextAuth JWT sessions are 30 days and 5 OAuth providers are wired. The next iteration is a second factor on the admin surface — TOTP as the baseline, WebAuthn passkey as the ergonomic upgrade — so a stolen session token is not enough on its own.

Evidencia actual: JWT + OAuth in place; second factor not yet enrolled

Acción de cierre: Enroll TOTP for admin role and ship WebAuthn passkey option as the ergonomic upgrade

Evaluated, not deployed

6 ítems

OCSP stapling on nginx TLS termination

Evaluated, not deployed

My TLS termination covers TLS 1.2/1.3, HSTS at 2 years, and 5 browser-side security headers. OCSP stapling is the next iteration — it removes the client-side dependency on the CA's responder so revocation checks are reliable even on hostile networks.

Evidencia actual: TLS + HSTS documented; OCSP stapling not yet configured

Acción de cierre: Add ssl_stapling + ssl_stapling_verify to the nginx TLS termination and document the change

Smurf mitigation: icmp_echo_ignore_broadcasts

Evaluated, not deployed

I drop all ICMP echo at the kernel level (icmp_echo_ignore_all=1), which also covers Smurf-style broadcast attacks. The narrower icmp_echo_ignore_broadcasts directive is a future refinement that adds defense-in-depth without changing the external posture.

Evidencia actual: icmp_echo_ignore_all=1 in place; broadcast-specific directive not yet added

Acción de cierre: Add icmp_echo_ignore_broadcasts=1 to the sysctl hardening block and document the rationale

auditd log immutability via filesystem attribute

Evaluated, not deployed

auditd config-watch is in production, but log immutability via `chattr +i` on /var/log/audit/ is the missing hardening step. Without it, an attacker with root can wipe audit trails; with it, even root cannot modify the trail in place. I want this on a monthly integrity check.

Evidencia actual: auditd config-watch in place; immutability check not yet wired

Acción de cierre: Add chattr +i hardening step to the auditd section + monthly immutability verification job

Suricata IDS alongside fail2ban

Evaluated, not deployed

fail2ban is reactive — it sees log evidence of an attack and bans afterward. Suricata is the pre-fact complement: signature-based IDS that drops or alerts on known patterns before the application sees the request. Evaluating Suricata is the next iteration.

Evidencia actual: fail2ban in production; Suricata evaluation not yet started

Acción de cierre: Evaluate Suricata deployment + signature update pipeline against my current packet budget

restic / btrfs / zfs filesystem snapshots

Evaluated, not deployed

Today I back up with age encryption plus hosting-provider snapshots. restic incremental backups with btrfs/zfs pre-hooks would close the gap between application-level state and filesystem-level state, so a database restore is consistent with the filesystem it lives on.

Evidencia actual: age encryption + hosting snapshots in place; restic/btrfs/zfs story not yet authored

Acción de cierre: Author the restic incremental backup plan with btrfs/zfs snapshot pre-hooks and document the restore drill

WAF evaluation (Coraza / ModSecurity)

Evaluated, not deployed

nginx security headers cover the browser-side holes. A WAF is the next layer — Coraza (native-Go) is the candidate because it can run as part of the Next.js edge middleware instead of as a separate sidecar. I want to evaluate it before committing.

Evidencia actual: Security headers in place; WAF not yet evaluated

Acción de cierre: Evaluate Coraza for Next.js edge middleware compatibility and document the verdict

Proposed, not implemented

3 ítems

Redis ACL + RDB + AOF + maxmemory-policy

Proposed, not implemented

Redis today uses keyPrefix isolation and client-side tracking. The next iteration adds per-user ACL, RDB snapshots for cold-start recovery, AOF for replay, and an explicit maxmemory-policy so eviction is deterministic instead of implicit.

Evidencia actual: keyPrefix + client-side tracking documented; ACL + persistence not yet configured

Acción de cierre: Add ACL configuration + RDB/AOF persistence policy + explicit maxmemory-policy and document it

Loki + Grafana centralized log aggregation

Proposed, not implemented

Log retention policies are documented per subsystem (90d nginx, 30d fail2ban, 0d WireGuard). Centralized aggregation is the next iteration so a single query can span all subsystems; the 30-day retention floor is the design target.

Evidencia actual: Per-subsystem retention documented; centralized aggregation not yet deployed

Acción de cierre: Deploy Loki + Grafana + Promtail with a 30-day retention floor and per-tenant access controls

ZTNA Level 2/3 with mTLS + OPA policy engine

Proposed, not implemented

My Zero-Trust architecture maps to NIST SP 800-207 Level 1 (the 6 logical components + 4 enforcement layers). Levels 2 and 3 add request-level policy decisions via OPA and service-to-service mTLS. The design is on the roadmap; the implementation is the next iteration.

Evidencia actual: NIST SP 800-207 Level 1 in place; mTLS + OPA roadmap only

Acción de cierre: Deploy OPA sidecar + sidecar-mediated mTLS for service-to-service calls and document the policy authoring flow

Upstream reference only

1 ítem

cgroup v2 sub-realm caps: CPUWeight + MemoryHigh + MemoryMax per slice

Upstream reference only

My self-hosted compile runners are scoped under cgroup v2 slices with explicit CPUWeight, MemoryHigh, and MemoryMax. Today the slice config lives in upstream kernel references; I want to commit my own slice template and the per-runner tree so reviewers can diff what I claim against what the kernel sees.

Evidencia actual: Upstream Linux kernel cgroup-v2 reference (kernel docs); slice template not yet authored

Acción de cierre: Author the per-runner slice tree + slice template; commit under docs/operations/ for diffable review

Production-only, not mirrored

2 ítems

Watchdog family with MemoryMax cgroup caps

Production-only, not mirrored

I run a family of watchdogs (mount, network, browser cache, etc.) with strict MemoryMax cgroup caps so a leak in any single watchdog cannot starve the rest of the host. The runtime source is in my operational toolkit; the next iteration is to lift the design notes into the docs repo so the rationale is shared.

Evidencia actual: Live in production memory; design notes not yet mirrored in docs

Acción de cierre: Add watchdog design notes (caps, restart policy, alert wiring) to docs/operations/

6-hour alert digest cron file

Production-only, not mirrored

The 6-hour digest cron pulls a summary of the prior window through the Telegram bot and the local LLM. The bot topology is documented; the cron file itself is the missing artifact, and committing it closes the loop so a fresh tenant can stand up the same cadence in one step.

Evidencia actual: Bot topology documented; cron file not yet committed

Acción de cierre: Commit the cron file + alert-digest-cron.sh under docs/operations/ for review and reuse

Curado el 2026-08-13 desde documentación interna de producción.