Envoy is a high-performance, open-source Layer 7 proxy and communication bus designed for large modern service-oriented architectures. Originally built at Lyft and now a graduated CNCF project, Envoy is the data plane behind Istio and most major service mesh implementations. It handles load balancing, observability, TLS termination, HTTP/2, gRPC proxying, circuit breaking, and much more – all configured through a flexible YAML-based or xDS API-driven model.
In this guide, we walk through installing and configuring Envoy Proxy on RHEL 10, Rocky Linux 10, and AlmaLinux 10. We cover both RPM-based installation and Docker deployment, build a working reverse proxy configuration from scratch, enable the admin interface, set up access logging, configure TLS termination, and cover basic rate limiting.
Prerequisites
Before you begin, make sure you have:
- A running RHEL 10, Rocky Linux 10, or AlmaLinux 10 server
- Root or sudo access
- A stable internet connection for downloading packages
- Basic familiarity with YAML syntax and Linux networking
- SELinux in enforcing or permissive mode (we cover adjustments where needed)
- Firewall access to configure required ports
Update your system packages before proceeding:
sudo dnf update -y
Verify your OS release:
cat /etc/redhat-release
You should see output similar to:
Rocky Linux release 10.0 (Obsidian Ocelot)
Method 1 – Install Envoy Proxy via Official RPM Repository
The Envoy project provides official RPM packages through the GetEnvoy repository maintained by Tetrate. This is the most straightforward way to get Envoy running on RHEL-based systems.
Step 1 – Add the Envoy RPM Repository
Create the Envoy yum repository file:
sudo tee /etc/yum.repos.d/envoy.repo <<'EOF'
[envoy]
name=Envoy Proxy
baseurl=https://rpm.dl.getenvoy.io/public/rpm/el/$releasever/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://rpm.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key
sslverify=1
EOF
Verify the repository was added:
sudo dnf repolist | grep envoy
Step 2 – Install Envoy
Install the Envoy package:
sudo dnf install -y getenvoy-envoy
If the GetEnvoy repository does not yet carry packages for RHEL 10, you can download the official Envoy binary directly from the project’s GitHub releases page as an alternative:
# Download the latest Envoy binary from func-e (Envoy distribution tool)
curl -L https://func-e.io/install.sh | bash -s -- -b /usr/local/bin
func-e use 1.32.3
sudo cp ~/.func-e/versions/1.32.3/bin/envoy /usr/local/bin/envoy
Verify the installation:
envoy --version
Expected output:
envoy version: 1.32.3/Clean/RELEASE/BoringSSL
Method 2 – Install Envoy Proxy via Docker
Running Envoy in a container is the most portable and commonly used approach in production environments, especially when you are already using container orchestration.
Step 1 – Install Docker or Podman
On RHEL 10 and its derivatives, Podman is available out of the box. You can use either Podman or Docker:
# Using Podman (available by default)
sudo dnf install -y podman
# Or install Docker CE
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io
sudo systemctl enable --now docker
Verify your container runtime:
podman --version
# or
docker --version
Step 2 – Pull the Envoy Image
docker pull envoyproxy/envoy:v1.32-latest
Verify the image:
docker run --rm envoyproxy/envoy:v1.32-latest --version
Step 3 – Run Envoy in a Container
Once we build a configuration file (covered in the next section), you can run Envoy in Docker like this:
docker run -d \
--name envoy \
--restart unless-stopped \
-p 10000:10000 \
-p 9901:9901 \
-v /etc/envoy/envoy.yaml:/etc/envoy/envoy.yaml:ro \
envoyproxy/envoy:v1.32-latest \
-c /etc/envoy/envoy.yaml
Verify the container is running:
docker ps | grep envoy
Configure Envoy from Scratch – envoy.yaml
Envoy uses a YAML configuration file that defines listeners, clusters, routes, and endpoints. Understanding these core concepts is important before writing any configuration.
Core Concepts
- Listener – A named network location (IP + port) where Envoy accepts incoming connections
- Filter Chain – A set of network filters applied to connections on a listener
- Route – Rules that match incoming requests and direct them to a cluster
- Cluster – A group of upstream hosts (endpoints) that Envoy can proxy traffic to
- Endpoint – An individual backend server address within a cluster
Create the Configuration Directory
sudo mkdir -p /etc/envoy
Build the Static Configuration
Create the main Envoy configuration file. This example sets up a reverse proxy that listens on port 10000 and forwards traffic to a backend application running on port 8080:
sudo tee /etc/envoy/envoy.yaml <<'EOF'
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend_service
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: backend_cluster
clusters:
- name: backend_cluster
type: STRICT_DNS
connect_timeout: 5s
load_assignment:
cluster_name: backend_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
EOF
Validate the configuration:
envoy --mode validate -c /etc/envoy/envoy.yaml
You should see:
configuration '/etc/envoy/envoy.yaml' OK
Understanding the Configuration
Let’s break down what each section does:
- admin – Exposes the Envoy admin interface on localhost port 9901 for stats, config dumps, and health checks
- listeners – Defines a single listener on all interfaces (0.0.0.0) at port 10000
- filter_chains – Applies the HTTP connection manager filter, which handles HTTP/1.1 and HTTP/2 traffic
- route_config – Routes all incoming requests (prefix “/”) to the backend_cluster
- clusters – Defines the backend_cluster pointing to 127.0.0.1:8080 with a 5-second connection timeout
Create a systemd Service Unit for Envoy
For RPM-based installations, create a systemd service to manage the Envoy process:
Step 1 – Create the Envoy System User
sudo useradd --system --no-create-home --shell /usr/sbin/nologin envoy
Step 2 – Set File Permissions
sudo chown -R envoy:envoy /etc/envoy
sudo mkdir -p /var/log/envoy
sudo chown -R envoy:envoy /var/log/envoy
Step 3 – Create the Service Unit File
sudo tee /etc/systemd/system/envoy.service <<'EOF'
[Unit]
Description=Envoy Proxy
Documentation=https://www.envoyproxy.io/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=envoy
Group=envoy
ExecStart=/usr/local/bin/envoy -c /etc/envoy/envoy.yaml --log-level info
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
EOF
Step 4 – Enable and Start Envoy
sudo systemctl daemon-reload
sudo systemctl enable --now envoy
Verify the service is running:
sudo systemctl status envoy
Expected output should show “active (running)”:
● envoy.service - Envoy Proxy
Loaded: loaded (/etc/systemd/system/envoy.service; enabled; preset: disabled)
Active: active (running) since ...
Configure the Firewall
Open the required ports in firewalld:
sudo firewall-cmd --permanent --add-port=10000/tcp
sudo firewall-cmd --permanent --add-port=9901/tcp
sudo firewall-cmd --reload
Verify the ports are open:
sudo firewall-cmd --list-ports
Test Envoy with curl
First, start a simple backend to test against. Python’s built-in HTTP server works well for this:
# Start a test backend on port 8080 in the background
mkdir -p /tmp/envoy-test
echo "Hello from backend" > /tmp/envoy-test/index.html
cd /tmp/envoy-test && python3 -m http.server 8080 &
Now test the Envoy proxy on port 10000:
curl -v http://localhost:10000/
You should see the response from the backend with Envoy-specific headers like x-envoy-upstream-service-time in the response:
< HTTP/1.1 200 OK
< server: envoy
< x-envoy-upstream-service-time: 1
...
Hello from backend
Admin Interface on Port 9901
The admin interface gives you real-time visibility into Envoy's state. Since we bound it to 127.0.0.1, it is only accessible from the local machine.
curl http://localhost:9901/
This returns an HTML page listing all available admin endpoints. Some useful ones:
# View cluster health status
curl http://localhost:9901/clusters
# View server stats
curl http://localhost:9901/stats
# View Envoy server info
curl http://localhost:9901/server_info
# Dump the running configuration
curl http://localhost:9901/config_dump
# View listener status
curl http://localhost:9901/listeners
# Check readiness
curl http://localhost:9901/ready
Verify the admin interface returns a 200 status:
curl -s -o /dev/null -w "%{http_code}" http://localhost:9901/ready
Expected output:
200
Security note: Never expose the admin interface to public networks. If you need remote access, use SSH tunneling:
ssh -L 9901:127.0.0.1:9901 user@your-server
Configure Access Logging
The basic configuration above sends access logs to stdout. For production, you typically want logs written to a file with a structured format.
Replace the access_log section in your envoy.yaml with:
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
log_format:
json_format:
timestamp: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
protocol: "%PROTOCOL%"
response_code: "%RESPONSE_CODE%"
response_flags: "%RESPONSE_FLAGS%"
upstream_host: "%UPSTREAM_HOST%"
duration: "%DURATION%"
request_id: "%REQ(X-REQUEST-ID)%"
user_agent: "%REQ(USER-AGENT)%"
Restart Envoy to apply the changes:
sudo systemctl restart envoy
Verify logs are being written:
curl http://localhost:10000/
tail -1 /var/log/envoy/access.log | python3 -m json.tool
You should see a formatted JSON log entry with all the fields defined in your configuration.
TLS Termination
Envoy can terminate TLS connections so your backend services do not need to handle certificates. This section shows how to configure HTTPS on the listener.
Step 1 - Prepare TLS Certificates
If you have certificates from a CA, place them in the Envoy configuration directory. For testing, generate a self-signed certificate:
sudo mkdir -p /etc/envoy/certs
sudo openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/envoy/certs/server.key \
-out /etc/envoy/certs/server.crt \
-subj "/CN=envoy.example.com"
sudo chown -R envoy:envoy /etc/envoy/certs
sudo chmod 600 /etc/envoy/certs/server.key
Verify the certificate:
openssl x509 -in /etc/envoy/certs/server.crt -text -noout | head -15
Step 2 - Update the Listener for TLS
Create a new listener configuration with TLS support. Add an HTTPS listener on port 8443 to your envoy.yaml:
listeners:
- name: listener_https
address:
socket_address:
address: 0.0.0.0
port_value: 8443
filter_chains:
- transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: /etc/envoy/certs/server.crt
private_key:
filename: /etc/envoy/certs/server.key
alpn_protocols:
- h2
- http/1.1
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_https
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: https_route
virtual_hosts:
- name: backend_service_tls
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: backend_cluster
Restart Envoy and open the firewall port:
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
sudo systemctl restart envoy
Test the HTTPS endpoint:
curl -k -v https://localhost:8443/
You should see a successful TLS handshake and the backend response.
Rate Limiting Basics
Envoy supports local rate limiting without requiring an external rate limit service. This is useful for protecting backends from excessive traffic.
Add the local rate limit filter to your HTTP filter chain, placing it before the router filter:
http_filters:
- name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append_action: OVERWRITE_IF_EXISTS_OR_ADD
header:
key: x-local-rate-limit
value: "true"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
This configuration allows 100 requests per 60-second window. When the limit is exceeded, Envoy returns a 429 (Too Many Requests) response.
After applying the configuration, restart Envoy and test:
sudo systemctl restart envoy
# Send multiple rapid requests to test rate limiting
for i in $(seq 1 110); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:10000/
done | sort | uniq -c
You should see 100 responses with code 200 and some with code 429.
Complete Production Configuration Example
Here is a full envoy.yaml that combines everything covered above - HTTP and HTTPS listeners, file-based access logging, local rate limiting, and the admin interface:
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 9901
static_resources:
listeners:
# HTTP listener on port 10000
- name: listener_http
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
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
log_format:
json_format:
timestamp: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
protocol: "%PROTOCOL%"
response_code: "%RESPONSE_CODE%"
duration: "%DURATION%"
upstream_host: "%UPSTREAM_HOST%"
http_filters:
- name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend_service
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: backend_cluster
# HTTPS listener on port 8443
- name: listener_https
address:
socket_address:
address: 0.0.0.0
port_value: 8443
filter_chains:
- transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: /etc/envoy/certs/server.crt
private_key:
filename: /etc/envoy/certs/server.key
alpn_protocols:
- h2
- http/1.1
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_https
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
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: https_route
virtual_hosts:
- name: backend_service_tls
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: backend_cluster
clusters:
- name: backend_cluster
type: STRICT_DNS
connect_timeout: 5s
load_assignment:
cluster_name: backend_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
Troubleshooting
Envoy Fails to Start
Check the logs for configuration errors:
sudo journalctl -u envoy -e --no-pager
Validate the configuration before restarting:
envoy --mode validate -c /etc/envoy/envoy.yaml
Port Already in Use
Check which process is using the port:
sudo ss -tlnp | grep 10000
Connection Refused to Backend
Verify the backend is running and accessible:
curl http://127.0.0.1:8080/
Check Envoy cluster health status:
curl http://localhost:9901/clusters | grep backend_cluster
Look for "health_flags" in the output. A healthy endpoint shows ::health_flags::healthy.
SELinux Blocking Envoy
If Envoy cannot bind to ports or read configuration files, SELinux may be the cause:
# Check for SELinux denials
sudo ausearch -m avc -ts recent
# Allow Envoy to bind to non-standard ports
sudo setsebool -P httpd_can_network_connect 1
# If needed, create a custom SELinux policy
sudo ausearch -c 'envoy' --raw | audit2allow -M envoy-custom
sudo semodule -i envoy-custom.pp
TLS Certificate Issues
Verify the certificate and key match:
openssl x509 -noout -modulus -in /etc/envoy/certs/server.crt | openssl md5
openssl rsa -noout -modulus -in /etc/envoy/certs/server.key | openssl md5
Both commands should return the same MD5 hash. If they differ, the certificate and key do not match.
High Memory or CPU Usage
Check Envoy stats for connection and request counts:
curl -s http://localhost:9901/stats | grep -E "downstream_cx_active|downstream_rq_active"
Review the memory allocation stats:
curl -s http://localhost:9901/memory
DNS Resolution Failures
If your cluster uses STRICT_DNS and the upstream hostname cannot be resolved:
# Check DNS from the server
dig your-backend-hostname
# Check Envoy DNS stats
curl -s http://localhost:9901/stats | grep dns
Consider switching the cluster type to STATIC and using IP addresses directly if DNS is unreliable.
Conclusion
You now have Envoy Proxy installed and configured on RHEL 10, Rocky Linux 10, or AlmaLinux 10. The setup covers a working reverse proxy with static configuration, systemd service management, the admin interface for monitoring, JSON access logging, TLS termination, and local rate limiting. From here you can expand your configuration with additional clusters, health checking, circuit breakers, or integrate with an xDS control plane for dynamic configuration management.




























































