How to Set Up Varnish Cache as a Reverse Proxy on RHEL 7
Varnish Cache is a high-performance HTTP reverse proxy and caching accelerator designed to sit in front of your web server. When a visitor requests a page, Varnish checks whether it holds a fresh cached copy. If it does, the response is served entirely from RAM in microseconds — the origin web server is never contacted. On RHEL 7 systems running Nginx, placing Varnish on port 80 and moving Nginx to port 8080 is the standard topology. This guide covers installation from the official Varnish yum repository, writing a practical VCL configuration, overriding the systemd unit to listen on port 80, and using varnishstat and varnishlog to verify everything is working correctly.
Prerequisites
- RHEL 7 server with root or sudo access
- Nginx already installed and serving content on port 80 (we will move it to 8080)
- Firewalld configured to allow traffic on ports 80 and 6082 (Varnish management)
- At least 1 GB of free RAM to allocate to the Varnish cache
- Active RHEL subscription or EPEL repository for any dependency packages
Step 1: Add the Varnish Yum Repository
The version of Varnish in the base RHEL 7 repositories is very old. The Varnish Software team provides a current repository for RHEL/CentOS 7:
sudo rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-7.4.el7.rpm 2>/dev/null ||
sudo curl -s https://packagecloud.io/varnishcache/varnish74/config_file.repo
-o /etc/yum.repos.d/varnish74.repo
# If the above repo is unavailable, use the EPEL version:
sudo yum install -y epel-release
sudo yum install -y varnish
For a production server, check https://packagecloud.io/varnishcache for the current repository URL for RHEL 7. Then install:
sudo yum install -y varnish
varnishd -V
# varnishd (varnish-7.x.x ...)
Step 2: Move Nginx to Port 8080
Before starting Varnish on port 80, reconfigure Nginx to listen on port 8080 so the two services do not conflict:
sudo vi /etc/nginx/nginx.conf
Change every listen 80 and listen [::]:80 directive to listen 8080 and listen [::]:8080. Also open server-specific config files in /etc/nginx/conf.d/ if they contain their own listen directives:
sudo grep -rn "listen 80" /etc/nginx/
# Edit each file found and change 80 to 8080
Allow SELinux to bind Nginx to a non-standard port:
sudo semanage port -a -t http_port_t -p tcp 8080
Reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Confirm Nginx is now on 8080:
ss -tlnp | grep 8080
Step 3: Write the Varnish VCL Configuration
Varnish is controlled by its Varnish Configuration Language (VCL). The default file lives at /etc/varnish/default.vcl. Replace its contents with a production-ready configuration:
sudo vi /etc/varnish/default.vcl
vcl 4.1;
import std;
# Backend definition — Nginx on localhost port 8080
backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 5s;
.first_byte_timeout = 60s;
.between_bytes_timeout = 10s;
.probe = {
.url = "/";
.timeout = 3s;
.interval = 10s;
.window = 5;
.threshold = 3;
}
}
# Called for every incoming client request
sub vcl_recv {
# Normalise the Host header to lowercase
set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
# Strip port from X-Forwarded-For if present
if (req.restarts == 0) {
if (req.http.X-Forwarded-For) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
# Only cache GET and HEAD requests
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# Do not cache requests with authorisation headers
if (req.http.Authorization) {
return (pass);
}
# Bypass cache for WordPress admin and login pages
if (req.url ~ "^/wp-(admin|login|cron|trackback|xmlrpc)") {
return (pass);
}
# Bypass for logged-in users (WordPress cookies)
if (req.http.Cookie ~ "wordpress_logged_in|comment_author|wp-postpass") {
return (pass);
}
# Remove cookies that do not affect page content (analytics, tracking)
set req.http.Cookie = regsuball(req.http.Cookie,
"(^|;s*)(_ga|_gid|_gat|_utm[a-z]+|__utm[a-z]+)=[^;]*", "");
set req.http.Cookie = regsub(req.http.Cookie, "^;s*", "");
# If no meaningful cookies remain, unset the header so Varnish will cache
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
return (hash);
}
# Defines the cache hash (key)
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
}
# Called after the backend returns a response
sub vcl_backend_response {
# Cache 404 pages for only 30 seconds to avoid storing errors long-term
if (beresp.status == 404) {
set beresp.ttl = 30s;
}
# Do not cache responses that tell us not to
if (beresp.http.Cache-Control ~ "private|no-cache|no-store") {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
# Cache HTML pages for 2 hours, static assets for 30 days
if (beresp.http.content-type ~ "text/html") {
set beresp.ttl = 2h;
} else if (bereq.url ~ ".(css|js|png|jpg|jpeg|gif|ico|woff2|woff|svg)$") {
set beresp.ttl = 30d;
}
# Strip Set-Cookie from cacheable responses so Varnish stores them
if (beresp.ttl > 0s) {
unset beresp.http.Set-Cookie;
}
return (deliver);
}
# Called before delivering the response to the client
sub vcl_deliver {
# Add a header to indicate whether the response was a cache HIT or MISS
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Remove internal Varnish headers from the client-facing response
unset resp.http.X-Varnish;
unset resp.http.Via;
return (deliver);
}
Step 4: Override the Systemd Unit to Listen on Port 80
By default on RHEL 7, Varnish listens on port 6081. We need to change this to port 80 using a systemd drop-in override — editing the unit file directly is not recommended as it will be overwritten on package upgrades:
sudo mkdir -p /etc/systemd/system/varnish.service.d
sudo vi /etc/systemd/system/varnish.service.d/customexec.conf
Add the following content, replacing the ExecStart line entirely:
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd
-a 0.0.0.0:80
-a 127.0.0.1:8443,PROXY
-f /etc/varnish/default.vcl
-s malloc,1G
-T 127.0.0.1:6082
Parameter notes:
- -a 0.0.0.0:80 — listen on all interfaces, port 80
- -s malloc,1G — allocate 1 GB of RAM for the object cache; adjust to suit your server
- -T 127.0.0.1:6082 — management interface on localhost only (used by
varnishadm)
Reload the systemd daemon and start Varnish:
sudo systemctl daemon-reload
sudo systemctl enable varnish
sudo systemctl start varnish
sudo systemctl status varnish
Allow port 80 through the firewall if not already open:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
Step 5: Allow SELinux to Connect to the Backend
On RHEL 7 with SELinux enforcing, Varnish must be permitted to make network connections to Nginx on port 8080:
sudo setsebool -P varnishd_connect_any 1
Step 6: Test with varnishstat
varnishstat displays real-time counters for cache hits, misses, backend connections, and more:
sudo varnishstat
Key counters to watch:
MAIN.cache_hit— total requests served from cacheMAIN.cache_miss— requests that went to the backendMAIN.n_object— number of objects currently in cacheMAIN.backend_req— total requests sent to Nginx
To see only the hit ratio in a one-shot snapshot:
sudo varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
Step 7: Debug Requests with varnishlog
varnishlog streams a live log of Varnish transactions, invaluable for understanding why a particular URL is not being cached:
# Stream all transactions
sudo varnishlog
# Filter to a specific URL
sudo varnishlog -q 'ReqURL ~ "/my-page"'
# Show only the hit/miss decision and reason
sudo varnishlog -q 'VCL_call' -g request
If a URL shows MISS repeatedly, look for ReqHeader Cookie lines — an unexpected cookie is the most common cause of cache bypasses.
Step 8: Purge a Cached Object
Send a PURGE request via varnishadm or directly with curl (you must allow PURGE in VCL):
# Add this to vcl_recv in default.vcl to enable purging from localhost:
# if (req.method == "PURGE") {
# if (client.ip == "127.0.0.1") { return (purge); }
# return (synth(405, "Not allowed"));
# }
# Then reload VCL and purge:
sudo varnishadm vcl.load newconfig /etc/varnish/default.vcl
sudo varnishadm vcl.use newconfig
curl -X PURGE http://127.0.0.1/path/to/page
Conclusion
Varnish Cache transforms a standard Nginx-backed RHEL 7 server into a high-throughput edge cache capable of handling tens of thousands of requests per second from a single modest machine. The VCL language gives you precise, programmable control over every caching decision — stripping analytics cookies so anonymous pages are cached, bypassing authenticated sessions, and setting differentiated TTLs for dynamic HTML versus static assets. Use varnishstat to monitor your hit ratio over time and aim for 80% or higher on a content-heavy site. With the systemd override in place your configuration survives package upgrades cleanly, and SELinux is kept in enforcing mode throughout.