← Back to DevBytes

Envoy Proxy Security Hardening and Best Practices

Understanding Envoy Proxy Security Hardening

Envoy proxy security hardening is the systematic process of reducing the attack surface of an Envoy deployment by tightening configuration, enabling protective filters, and enforcing strict operational defaults. Out of the box, Envoy provides a powerful, flexible data plane, but its default settings prioritize compatibility and ease of use over strict security. Hardening transforms a standard Envoy setup into a hardened ingress/egress gateway that resists common threats such as protocol-level attacks, unauthorized access, credential exposure, and resource exhaustion.

This tutorial covers the core areas you need to lock down: TLS termination and mTLS, administrative interface protection, authorization and rate limiting, network binding, secure logging, filter sandboxing, and request validation. Each section includes practical YAML configuration snippets that you can adapt directly to your own envoy.yaml or dynamic control-plane resources.

Why Security Hardening Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Envoy often sits at a critical network boundary—as a front proxy for Kubernetes ingress, a service mesh sidecar, or an edge gateway. A weak configuration exposes you to:

Applying the practices below turns Envoy into a robust, defense-in-depth layer that actively protects your services rather than merely proxying traffic.

How to Implement Security Hardening

1. Enforce Strict TLS and mTLS

Always terminate TLS at the Envoy listener and enforce a minimum TLS version along with strong cipher suites. For east‑west service mesh traffic, use mutual TLS (mTLS) to authenticate both client and server. The following snippet shows a downstream listener that requires TLS 1.2+ and restricts ciphers to modern, secure suites:


static_resources:
  listeners:
  - name: ingress_listener
    address:
      socket_address: { address: 0.0.0.0, port_value: 443 }
    filter_chains:
    - filter_chain_match:
        server_names: ["api.example.com"]
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
          common_tls_context:
            tls_params:
              tls_minimum_protocol_version: TLSv1.2
              tls_maximum_protocol_version: TLSv1.3
              cipher_suites:
              - ECDHE-ECDSA-AES256-GCM-SHA384
              - ECDHE-RSA-AES256-GCM-SHA384
              - ECDHE-ECDSA-AES128-GCM-SHA256
              - ECDHE-RSA-AES128-GCM-SHA256
            tls_certificates:
            - certificate_chain:
                filename: "/etc/envoy/certs/server.crt"
              private_key:
                filename: "/etc/envoy/certs/server.key"
            require_client_certificate: false   # set to true for mTLS
      filters:
      - name: envoy.filters.network.http_connection_manager
        ...

For upstream connections (Envoy → backend), configure an UpstreamTlsContext similarly, and set require_client_certificate: true on downstream when you want mTLS. In a service mesh, automate certificate rotation via SDS (Secret Discovery Service) instead of static files.

2. Lock Down the Administration Interface

Envoy's admin interface is a powerful HTTP endpoint (default port 9901) that returns configuration dumps, metrics, and even allows runtime changes. It must never be exposed to untrusted networks. Best practices include:


admin:
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }
  access_log:
  - name: envoy.access_loggers.file
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
      path: "/var/log/envoy_admin.log"
  config_overrides:
    # Optional: require a static shared secret in X-Admin-Secret header
    mutate_config_headers:
    - key: X-Admin-Secret
      value: "your-long-random-secret"

In production, consider moving admin operations to a separate, authenticated listener that uses mTLS and restrict access via network policies.

3. Apply Network-Level Binding Restrictions

Never bind listeners to 0.0.0.0 unless strictly necessary. Prefer explicit IP addresses or restrict via Linux capabilities and firewall rules. For Kubernetes, use hostPort or containerPort combined with NetworkPolicy.


listener:
  address:
    socket_address: { address: 192.168.1.10, port_value: 443 }

Additionally, use the bind_to_port option (default true) to require explicit binding. Avoid exposing Envoy on the same port as application pods without TLS.

4. Enable RBAC and Authorization Filters

Envoy’s Role-Based Access Control (RBAC) HTTP filter lets you define fine-grained permissions based on source IP, headers, JWT claims, or mTLS principal. Use it to enforce zero-trust at the proxy layer. The example below allows only requests from the subnet 10.0.0.0/8 with a valid X-Team header:


http_filters:
- name: envoy.filters.http.rbac
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
    rules:
      action: ALLOW
      policies:
        restrict_access:
          permissions:
          - and_rules:
              rules:
              - header:
                  name: X-Team
                  present_match: true
              - header:
                  name: X-Team
                  string_match:
                    exact: "backend"
          principals:
          - source_ip:
              address_prefix: 10.0.0.0
              prefix_len: 8
    shadow_rules:
      action: DENY
      policies: {} # log-only denied requests if needed

Combine RBAC with external authorization (ext_authz filter) for JWT validation or OPA policies.

5. Implement Rate Limiting and Circuit Breaking

Protect your backends from abuse by applying rate limits at the listener or route level. Use the envoy.filters.http.rate_limit filter or route-specific rate_limits actions. Circuit breakers on clusters prevent cascading failures.


route_config:
  virtual_hosts:
  - name: api_services
    domains: ["*"]
    routes:
    - match: { prefix: "/" }
      route:
        cluster: backend_service
        rate_limits:
        - actions:
          - remote_address: {}
    ...
clusters:
- name: backend_service
  connect_timeout: 3s
  circuit_breakers:
    thresholds:
    - priority: DEFAULT
      max_connections: 500
      max_pending_requests: 100
      max_requests: 1000
      max_retries: 3

For global rate limiting, deploy a dedicated rate limit service and reference it via the rate_limit_service configuration.

6. Secure Access Logging and Avoid Sensitive Data Leakage

Access logs are invaluable for auditing, but they can accidentally expose headers, cookies, or query parameters. Hardening means carefully selecting log format strings and sanitizing or omitting sensitive fields.


access_log:
- name: envoy.access_loggers.file
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
    path: "/var/log/envoy_access.log"
    format: |
      [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% "%UPSTREAM_HOST%" %DURATION%
    # Omit headers like Cookie, Authorization, Set-Cookie by not including them

Use %REQ(header-name)% sparingly. Never log the Authorization header or Cookie in plaintext. Consider using Envoy's sanitization filter (envoy.filters.http.header_sanitization) to strip sensitive headers before logging.

7. Validate Requests and Prevent HTTP Smuggling

Envoy has strong HTTP parsing defaults, but you should still enforce strict validation. Disable HTTP/1.0 if not needed, reject malformed headers, and enable request smuggling detection.


http_connection_manager:
  http_protocol_options:
    accept_http_10: false
  normalize_path: true
  merge_slashes: true
  strip_any_host_port: true
  common_http_protocol_options:
    idle_timeout: 60s
    headers_with_underscores_action: REJECT_REQUEST
  route_config: ...

Set headers_with_underscores_action to REJECT_REQUEST because many backends treat underscores as hyphens, creating injection risks. Enable envoy.filters.http.header_validation to block non-RFC compliant characters.

8. Sandbox Custom Filters (WASM/Lua)

If you extend Envoy with WebAssembly (WASM) or Lua scripts, isolate them in a sandbox with restricted capabilities. Avoid giving Lua scripts access to HTTP callouts unless necessary, and always use the envoy.filters.http.wasm filter with minimal capability sets.


- name: envoy.filters.http.wasm
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
    config:
      vm_config:
        runtime: "envoy.wasm.runtime.v8"
        code:
          local:
            filename: "/etc/envoy/filter.wasm"
        sandbox_config:
          # Restrict access to network, file system, environment variables
          http_callout_config:
            enabled: false
          capabilities: []

Always review WASM modules for vulnerabilities and keep them updated. Prefer built-in filters over custom code where possible.

Best Practices Summary

Conclusion

Envoy proxy security hardening is not a one-time task—it’s an ongoing discipline that evolves with your architecture and the threat landscape. By enforcing strict TLS, locking down the admin interface, applying authorization and rate limiting, binding to restricted interfaces, sanitizing logs, and validating every request, you transform Envoy from a simple proxy into a formidable security control point. Combine these measures with regular updates, non-root execution, and network segmentation to build a resilient, defense-in-depth ingress and service mesh layer. Start with the most impactful items—TLS and admin protection—and gradually adopt the remaining practices to continuously reduce your exposure.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles