Skip to main content
  1. Posts/

Securing /wp-admin in Kubernetes with Traefik and Split-DNS

·308 words·2 mins
Author
Daniel Lincu
I build, automate, and document Kubernetes infrastructure in my TechCats Homelab.

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
#

Vault metrics in Grafana dashboard
Figure 1: Traefik dashboard confirming the IP allowlist middleware is active for /wp-admin and /wp-login.php, allowing only local network access.

Key Takeaways
#

  • externalTrafficPolicy: Local preserves 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-admin and /wp-login.php requests
  • Verified results through accurate Traefik access logs

A lightweight and effective approach using Traefik, Pi-hole, and Kubernetes networking.