A listener binds a socket address, accepts connections, and hands each one to the filter chain that best matches it. Configure one listener per port Arion serves, and use multiple filter chains when a single port must present different certificates, tenants, or protocols.
Enable a listener
envoy_bootstrap:
static_resources:
listeners:
- name: listener_http
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- name: default_chain
filters:
- name: http_gateway
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: HTTP1
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_routes
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: api }
A listener needs a unique name, an address, and at least one filter chain. Each chain runs optional network filters — RBAC, connection limit, global rate limit — followed by exactly one terminal filter: an HTTP connection manager or a TCP proxy. tcp_backlog_size (default 128) sizes the accept queue, and socket_options may carry one bind-device entry (a Linux SO_BINDTODEVICE interface name).
Sockets, pipes, and internal listeners
Listeners bind TCP socket addresses. Unix pipes sit on the other side of the proxy: a cluster endpoint may use a pipe address, so Arion can accept on a TCP port and forward to a local process over a Unix socket. Inside a cluster's load_assignment:
lb_endpoints:
- endpoint:
address:
pipe:
path: /var/run/backend.sock
A listener declared with internal_listener: {} in place of an address accepts connections from inside the proxy rather than from the network; clusters reach it through endpoints with internal addresses. Binding a listener to a pipe address is rejected at startup.
Multiple filter chains
When a listener has several filter chains, Arion picks one per connection using filter_chain_match:
| Field | Matches |
|---|---|
destination_port | Port the connection arrived on |
prefix_ranges | Destination IP, CIDR |
server_names | TLS SNI, exact or *.example.com wildcard |
source_prefix_ranges | Client IP, CIDR |
source_ports | Client port |
The most specific chain wins: a chain that states no rule for a criterion stays eligible, a chain whose rule fails is out, and among matches the tighter value (longer CIDR prefix, more exact SNI) is preferred. All match blocks in a listener must be unique, and a chain with no filter_chain_match is the catch-all:
filter_chains:
- name: api_chain
filter_chain_match:
server_names: [api.example.com]
# filters for api traffic ...
- name: catch_all
# no filter_chain_match: everything else lands here
Envoy's transport_protocol, application_protocols, source_type, and default_filter_chain fields are not supported; a config that uses them is refused at startup.
Matching on
server_namesrequires the TLS inspector listener filter. Arion never adds it implicitly — a listener that matches on SNI withoutenvoy.filters.listener.tls_inspectorfails at startup, because the matches could never succeed.
TLS on a filter chain
TLS terminates per filter chain through a transport_socket, so each chain can present its own certificate:
filter_chains:
- name: api_chain
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_certificate_sds_secret_configs:
- name: api_server_cert
filters:
# terminal filter ...
Set require_client_certificate: true for mTLS. Certificates can be inlined, read from files, or referenced as SDS secrets that rotate without a restart — see TLS and mTLS and secrets and rotation.
Listener filters
Listener filters inspect the raw connection before a filter chain is chosen. Arion supports three: the TLS inspector reads the SNI from the TLS ClientHello so chain matching (and the global rate limiter's SNI-as-domain mode) can use it; proxy protocol recovers the original client address from a PROXY v1/v2 header; and the listener-level local rate limit drops connections above a token-bucket rate before any other work happens — see rate limiting for choosing a limiter.
Verify
With the admin interface on port 9901, list bound listeners:
curl -s http://127.0.0.1:9901/listenerslistener_http::0.0.0.0:8080Limits
- Listeners are TCP only;
udp_listener_config(QUIC) is rejected. - One terminal filter per chain, always last; RBAC, connection limit, and global rate limit must precede it.
- At most one bind device per listener.
listener_filters_timeout,use_original_dst, and per-connection buffer limits are not supported and are refused at startup.- Pipe addresses work for cluster endpoints, not listener binds.
Every listener field is tabulated in the listener reference.