Overview and goal#
WordPress runs inside Kubernetes behind Traefik v3 as the ingress controller.
The goal was to restrict access to /wp-admin and /wp-login.php to the local network while denying all external traffic.
Solution#
1. Middleware: Allow Only LAN + Localhost#
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: wp-admin-allowlist
namespace: wordpress-prod
annotations:
traefik.io/middleware-log-level: DEBUG
spec:
ipAllowList:
sourceRange:
- "192.168.10.0/24"
- "127.0.0.1/32"
2. Preserve the Real Client IP#
By default, Kubernetes LoadBalancer services use SNAT (externalTrafficPolicy: Cluster), which hides client IPs.
service:
enabled: true
type: LoadBalancer
spec:
loadBalancerIP: 192.168.10.241
externalTrafficPolicy: Local
3. Split-DNS with Pi-hole#
Pi-hole resolves techcats.org directly to the MetalLB IP (192.168.10.241) inside the LAN, while external users still go through Cloudflare.
This cleanly separates internal and external traffic without separate domains.
4. Traefik Additional Arguments#
additionalArguments:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--api.dashboard=true"
- "--log.level=INFO"
- "--accesslog=true"
- "--accesslog.fields.headers.defaultmode=keep"
Keeping all request headers was useful for verifying forwarded IPs.
5. IngressRoute: Apply Middleware to Admin Paths#
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: wordpress
namespace: wordpress-prod
spec:
entryPoints:
- websecure
routes:
- match: Host(`techcats.org`)
kind: Rule
services:
- name: wordpress-prod
port: 80
- match: Host(`techcats.org`) && (PathPrefix(`/wp-admin`) || Path(`/wp-login.php`))
kind: Rule
middlewares:
- name: wp-admin-allowlist
namespace: wordpress-prod
services:
- name: wordpress-prod
port: 80
tls:
secretName: wordpress-tls-secret
Verification#

/wp-admin and /wp-login.php, allowing only local network access.Key Takeaways#
externalTrafficPolicy: Localpreserves real client IPs for allowlists.- Split-DNS via Pi-hole provides clean LAN vs public separation.
- Traefik’s access logs help confirm forwarded headers and IPs.
- Middleware scope is critical—apply it only where needed.
Outcome#
- Secure WordPress administration from LAN only
- Blocked all external
/wp-adminand/wp-login.phprequests - Verified results through accurate Traefik access logs
A lightweight and effective approach using Traefik, Pi-hole, and Kubernetes networking.
