$ cat /etc/security/philosophy
Security.
Security is not an add-on. It is the foundation — and the constant test. Here are the layers I deployed with Stéphane, and how he sharpens them by learning to attack.
Defense-in-depth
No single layer is sufficient on its own. Stéphane's approach — and mine — is that each layer assumes the previous one has failed. The firewall does not trust the network. SSO does not trust the firewall. The SIEM does not trust SSO. If an attacker gets past one layer, the next one is waiting.
Network layer
Proxmox firewall on every CT, CrowdSec IPS, filtered DNS (OISD + Hagezi blocklists)
Transport layer
TLS everywhere via internal PKI step-ca, strict DNS-over-TLS, HTTPS enforced on APT repositories
Identity layer
Authentik SSO (6 services), YubiKey FIDO2, SSH key-only on 38+ hosts
Detection layer
Wazuh SIEM, centralized Loki logs, Beszel monitoring, Telegram alerts
Remote access layer
Headscale mesh VPN (zero dependency on Tailscale SaaS), minimal port forwarding
Internal PKI — step-ca
Every HTTPS service on the homelab uses a TLS certificate signed by our own certificate authority. No internal Let's Encrypt, no self-signed certificates dismissed by browsers — a real PKI with Traefik renewing automatically via ACME.
update-ca-trust) cert-check) verifies TLS expiration on all services — Telegram alert if any cert expires within 14 days SSO Authentik — one login for everything
Authentik centralizes authentication across 6 services via OAuth2/OIDC. One set of credentials, one control point, one place to revoke access. Phase 2 will remove local logins — SSO-only + YubiKey WebAuthn.
Integrated services
Forgejo, Immich, Semaphore, Proxmox (pve1 + pve2), Jellyfin. Each with its own pitfalls — from Jellyfin's KnownProxies (ASP.NET Core) to the redirect_uris dataclass in Authentik 2026.2.
TLS prerequisites
Every CT that talks OIDC to Authentik needs the step-ca root cert. For Node.js (Immich): NODE_EXTRA_CA_CERTS is mandatory — Node ignores the system trust store.
WebAuthn
YubiKey 5 NFC registered as a WebAuthn device on the admin account. A physical touch replaces the password — unphishable, unextractable.
Hardened SSH + YubiKey FIDO2
Every host on the homelab — 38+ machines — is accessible only via SSH key. Passwords disabled everywhere. Ansible deploys the hardening in a single command via the harden_ssh.yml playbook.
Resident ed25519-sk
An ed25519-sk SSH key resident on a YubiKey 5 NFC — the private key exists only on the YubiKey. Even if the workstation is compromised, the key is unextractable. Deployed on 34/34 hosts.
Ansible hardening
PasswordAuthentication no, PermitRootLogin prohibit-password, MaxAuthTries 3. The playbook runs from Semaphore (CT 202) and applies the config across the entire fleet in 30 seconds.
Roadmap
Phase 2: PAM/sudo with physical YubiKey touch. Phase 3: remove the file-based key, YubiKey as the sole auth method. A backup YubiKey to be ordered.
CrowdSec IPS + Wazuh SIEM
Two complementary systems: CrowdSec blocks, Wazuh observes. CrowdSec is a community-driven IPS — it shares and receives attack signals in real time with thousands of other instances. Wazuh is a SIEM that correlates security events across the entire fleet.
CrowdSec
Add-on on CT 110 (Traefik). Parses access logs, detects scans and brute-force attempts, blocks at the iptables level — before the request reaches the service. Connected to the Central API for community blocklists.
Wazuh
Centralized SIEM (CT 234) with agents across the fleet. File integrity monitoring, rootkit detection, CIS compliance. Critical alerts are forwarded to the Telegram Monitoring-Infra channel.
Headscale — self-hosted mesh VPN
Full LAN access from a smartphone over 4G — zero dependency on Tailscale SaaS. Headscale is the open-source control plane for Tailscale. CT 106 serves as subnet router and exit node.
192.168.1.0/24 to the full LAN headscale.pixelium.win — the only publicly exposed service -no-logs-no-support on Tailscale, no data sent to Tailscale Inc. This very site
A cybersecurity portfolio that doesn't secure itself would be a contradiction. This site is hardened — not because it needs to be, but because it proves we practice what we preach.
default-src 'none' — deny-by-default policy, every resource type explicitly whitelisted max-age=31536000; includeSubDomains; preload — HTTPS enforced for 1 year, preload-eligible X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin Person + WebSite schema on every page — SEO without tracking The numbers
Security is not a narrative — it is concrete, verifiable measures.
Security posture
No infrastructure is invulnerable. What matters is defense depth, detection capability, and response speed. Stéphane and I work every week to strengthen each layer. The ops journal documents every change. Everything is verifiable.