Squid + E2guardian Filtering and Caching Proxy on Ubuntu 24.04 on Azure User Guide
Overview
This image pairs two mature open source proxies on a single virtual machine to give you a filtered and caching forward web proxy that works the moment it boots. E2guardian is the content filtering engine: it sits between your users and the internet and applies policy to every request, filtering by domain, url, file extension, MIME type and weighted phrase content, and returning a clear block page when a request is refused rather than failing silently. Squid sits behind it as the caching upstream, storing frequently requested content to cut bandwidth and latency. Every transaction is written to a structured access log, so outbound browsing is both governed and auditable.
The cloudimg image builds E2guardian 5.5.9r from upstream source and runs it as the e2guardian.service systemd unit, listening on port 8080. It installs Squid 6.x from the Ubuntu archive as squid.service, listening on 127.0.0.1:3128. E2guardian is configured with proxyip = 127.0.0.1 and proxyport = 3128, so it forwards every permitted request to the local Squid, which fetches from origin or serves from its cache.
E2guardian is the only client facing listener. Clients point at port 8080; Squid is bound to the loopback interface only, so it is never reachable off the machine. It exists solely as e2guardian's caching upstream. That means one appliance both governs and accelerates outbound browsing, with no second proxy for you to deploy or expose.
It is not an open relay, and that is enforced rather than documented. An exposed proxy is one of the most abused assets on the internet. E2guardian has no source address allowlist of its own and binds all interfaces by default, so this image ships e2guardian-firewall.service, an nftables allowlist that accepts port 8080 only from loopback and RFC1918 client networks and drops every other source. e2guardian.service declares Requires=e2guardian-firewall.service and Requires=squid.service, so if that allowlist cannot be applied, or the caching upstream is down, the proxy does not start at all. Scope your Azure network security group to the same client range as the second layer.
No TLS interception, so no certificate authority key exists. E2guardian can be run as a man in the middle to inspect HTTPS payloads, which requires a certificate authority certificate and private key. A CA key baked into a marketplace image would be shared by every customer of that image, so this appliance ships with interception off and no CA material whatsoever, at build time or first boot. HTTPS is still filtered, by the hostname in the CONNECT request against your site lists.
No login, no bootstrap password. E2guardian is a headless daemon with no web console, and Squid is controlled by source address rather than a proxy password, so there is nothing to sign in to and no default credential exists to be found. Administration is over your own key based SSH.
What is included:
- E2guardian 5.5.9r (GPL-2.0) built from a sha256 pinned upstream source tarball
- Squid 6.x (GPL-2.0) from the Ubuntu 24.04 archive, as the caching upstream
e2guardian.servicerunning the filtering proxy on port8080squid.servicerunning the caching upstream on127.0.0.1:3128(loopback only)e2guardian-firewall.service, a fail closed nftables source allowlist- A working, self demonstrating filter policy so you can prove filtering on first boot
- Structured access logs at
/var/log/e2guardian/access.logand/var/log/squid/access.log squid-e2guardian-firstboot.servicefor per VM proxy facts and request loop prevention- The complete corresponding source and GPL-2 written offer shipped on the image
- Ubuntu 24.04 LTS base, fully patched, unattended security upgrades enabled
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet that your client machines sit on. The bundle is efficient; Standard_B2s (2 vCPU, 4 GB RAM) comfortably handles a small to medium user population. The Squid cache and both access logs live on the OS disk, so expand the disk if you intend to run a large cache or retain a long browsing history locally rather than forwarding it to a SIEM.
Step 1: Deploy the Virtual Machine
Option A: Azure Portal
Search the Marketplace for Squid + E2guardian Filtering and Caching Proxy on Ubuntu 24.04, choose your VM size, and provide your SSH public key. Attach a network security group that allows:
- TCP
22(admin SSH) from your management network only. - TCP
8080(the proxy) from your client subnet only. Never fromInternetorAny. An open proxy is found and abused within hours.
There is no NSG rule for port 3128. Squid listens on the loopback interface only and is never reached from off the machine.
Option B: Azure CLI
The rule that matters here is allow-proxy-clients: it is scoped to a client CIDR, never to Internet.
RG="squid-e2guardian-prod"; LOCATION="eastus"; VM_NAME="filterproxy-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/squid-e2guardian-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name fp-vnet --address-prefix 10.60.0.0/16 --subnet-name fp-subnet --subnet-prefix 10.60.1.0/24
az network nsg create -g "$RG" --name fp-nsg
az network nsg rule create -g "$RG" --nsg-name fp-nsg --name allow-admin-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name fp-nsg --name allow-proxy-clients --priority 110 \
--source-address-prefixes "10.60.1.0/24" --destination-port-ranges 8080 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name fp-vnet --subnet fp-subnet --nsg fp-nsg --public-ip-sku Standard
Step 2: Connect via SSH
ssh azureuser@<vm-ip>
Find the public IP with az vm show -d -g squid-e2guardian-prod -n filterproxy-01 --query publicIps -o tsv.
Step 3: Verify the proxy, its cache and its access control are running
Three units must be active. The filtering proxy is deliberately coupled to both the allowlist and the caching upstream, so a healthy e2guardian implies a healthy squid and a healthy allowlist.
sudo systemctl is-active squid e2guardian e2guardian-firewall
ss -tln | grep -E ':8080 |:3128 |:22 ' || true
e2guardian -v | head -1
squid -v | head -1
echo "stack verified"
You should see all three units reporting active, e2guardian listening on 0.0.0.0:8080, Squid listening on 127.0.0.1:3128 (loopback only), your admin SSH on 0.0.0.0:22, and the two version banners.
active
active
active
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
LISTEN 0 256 127.0.0.1:3128 0.0.0.0:*
LISTEN 0 256 0.0.0.0:8080 0.0.0.0:*
e2guardian 5.5.9r
Squid Cache: Version 6.14

Step 4: Prove that filtering actually works
This is the important step. A running service proves nothing about a filtering proxy, and a status code proves nothing either, because a block page and a real page can both be returned successfully. The check below compares the content returned for a permitted site against the content returned for a blocked one, both fetched through the proxy.
The image ships exactly one demo block entry: example.org, which is IANA's reserved documentation domain, so blocking it affects no real service. example.com is not blocked, and both serve the same page when unfiltered, which makes them a clean pair for the test.
printf 'ALLOWED example.com -> '
curl -s -m 20 -x http://127.0.0.1:8080 http://example.com/ | grep -o '<h1>[^<]*</h1>'
printf 'BLOCKED example.org -> '
curl -s -m 20 -x http://127.0.0.1:8080 http://example.org/ | grep -o '<title>[^<]*</title>'
echo "filtering verified"
The permitted request returns the real page (<h1>Example Domain</h1>). The blocked request returns E2guardian's own block page (<title>E2Guardian - Access Denied</title>) and the real page content is absent entirely. That difference is the product doing its job.
ALLOWED example.com -> <h1>Example Domain</h1>
BLOCKED example.org -> <title>E2Guardian - Access Denied</title>

Step 5: See the caching upstream at work
E2guardian does not fetch from the internet itself in this image; it forwards permitted requests to the local Squid, which fetches or serves from its cache. You can see both halves of that.
First, the request you just allowed appears in Squid's access log, which proves e2guardian forwarded it to the caching upstream rather than going straight to origin:
grep -m1 'example.com' /var/log/squid/access.log
1786236560.021 14 <client> TCP_MISS/200 929 GET http://example.com/ - HIER_DIRECT/<origin> text/html
Then, to see a genuine cache hit, fetch a cacheable object through Squid twice. TCP_HIT or TCP_MEM_HIT in the log means the repeat was served from cache rather than refetched:
D=$(mktemp -d); echo "cache probe" > "$D/probe.txt"
( cd "$D" && python3 -m http.server 18080 >/dev/null 2>&1 & echo $! > /tmp/origpid )
sleep 1
curl -s -o /dev/null -x http://127.0.0.1:3128 http://127.0.0.1:18080/probe.txt
curl -s -o /dev/null -x http://127.0.0.1:3128 http://127.0.0.1:18080/probe.txt
sleep 1
grep 'probe.txt' /var/log/squid/access.log | awk '{print $4, $7}' | tail -2
kill "$(cat /tmp/origpid)" 2>/dev/null
echo "caching verified"
TCP_HIT/200 http://127.0.0.1:18080/probe.txt
TCP_MEM_HIT/200 http://127.0.0.1:18080/probe.txt
Filtering and caching, in one appliance.

Step 6: Confirm the appliance is not an open relay
The allowlist accepts loopback and RFC1918 sources and drops everything else. You can exercise the real rule with a genuine foreign source address: 203.0.113.7 is from TEST-NET-3, a range reserved for documentation, so it can never collide with your own network.
The first request is the positive control. If a permitted source could not relay either, a refusal would prove nothing.
sudo nft list table inet e2guardian
H=$(hostname -I | awk '{print $1}')
sudo ip addr add 203.0.113.7/32 dev lo 2>/dev/null || true
printf 'permitted source %s -> ' "$H"
curl -s -m 15 --interface "$H" -x http://$H:8080 http://example.com/ | grep -o '<h1>[^<]*</h1>'
printf 'unauthorised source 203.0.113.7 -> '
curl -s -m 8 --interface 203.0.113.7 -x http://$H:8080 http://example.com/ >/dev/null 2>&1 && echo "RELAYED" || echo "refused by the source allowlist"
sudo ip addr del 203.0.113.7/32 dev lo 2>/dev/null || true
echo "relay refusal verified"
The permitted private address relays successfully; the unauthorised address is dropped. To serve clients from a range outside RFC1918, add it to the e2g_allowed set in /etc/e2guardian/cloudimg-firewall.nft and run sudo systemctl restart e2guardian-firewall, and widen your NSG rule to match.

Step 7: Read the access logs
Every request, allowed or denied, is recorded. E2guardian's log carries the filtering decision; Squid's log carries the cache decision. Denied entries carry the reason and the list that matched, which is what makes the appliance auditable.
sudo tail -n 3 /var/log/e2guardian/access.log
echo "access log verified"
Each e2guardian line records the timestamp, client address, url, method, status and, for a denial, the reason such as *DENIED* Blocked site (local): example.org. Forward both logs to your SIEM with any standard log shipper.
Step 8: Read this VM's instance note
The first boot service wrote a root only note describing this VM's proxy endpoint, the access control in force, and the TLS posture.
sudo cat /root/squid-e2guardian-info.txt
echo "instance note verified"
The note is owned root:root with mode 0600. It states plainly that the appliance has no login and no bootstrap credential, and records the private proxy endpoint. No public proxy endpoint is advertised, because clients should always reach the proxy over your own network.
Step 9: Set your own filtering policy
The block list is a plain text file, one domain per line. A bare domain also covers its subdomains.
E2G_LISTS=$(sed -nE 's/^[[:space:]]*\.Define[[:space:]]+LISTDIR[[:space:]]*<([^>]+)>.*/\1/p' /etc/e2guardian/e2guardianf1.conf | head -1)
echo "your block list is at $E2G_LISTS/localbannedsitelist"
sudo grep -v '^#' "$E2G_LISTS/localbannedsitelist" | grep -v '^$'
echo "block list located"
Add your own domains to that file, remove the example.org demo line when you no longer need it, and apply the change:
sudo systemctl reload e2guardian
echo "policy reloaded"
E2guardian also ships upstream's category and weighted phrase lists in the same directory (bannedsitelist, bannedurllist, weightedphraselist and others), which you can populate with a commercial or community blocklist feed. The localbannedsitelist used above is upstream's dedicated file for local administrator additions, so your entries stay separate from any feed you import.
Step 10: Point a client at the proxy
From a machine on your client subnet, set the standard proxy environment variables, or configure the proxy in your browser or operating system network settings. Point them at the e2guardian port 8080, never at Squid.
PROXY=$(sudo sed -n 's/^PROXY_URL=//p' /root/squid-e2guardian-info.txt)
echo "using proxy $PROXY"
export http_proxy="$PROXY"
export https_proxy="$PROXY"
curl -s -o /dev/null -w 'allowed site -> HTTP %{http_code}\n' http://example.com/
curl -s http://example.org/ | grep -o '<title>[^<]*</title>'
echo "client proxy configuration verified"
Run from a client machine, using the private address from the instance note. The allowed site returns HTTP 200 and the blocked site returns the E2Guardian block page, confirming the client is genuinely going through the filter and its cache rather than straight out to the internet. HTTPS requests are filtered by the hostname in the CONNECT request, so domain policy applies to HTTPS as well as HTTP, without any interception of the encrypted payload.
Tuning the cache
Squid's cache size and memory are set in /etc/squid/squid.conf (cache_dir and cache_mem). After editing, validate and apply without dropping connections:
sudo squid -k parse
sudo systemctl reload squid
echo "squid config applied"
Squid is deliberately bound to 127.0.0.1:3128. Do not change it to a routable address: it is an internal component reached only by e2guardian, and exposing it would bypass the content filter entirely. The single client facing surface is e2guardian on port 8080.
Managing the services
sudo systemctl status e2guardian squid e2guardian-firewall
sudo systemctl reload e2guardian
sudo systemctl restart e2guardian
sudo journalctl -u e2guardian -n 50 --no-pager
reload re-reads the e2guardian lists without dropping connections, which is what you want after a policy edit. Note that stopping e2guardian-firewall or squid will also stop the filtering proxy, because it requires both.
Server Components
| Component | Version | Purpose |
|---|---|---|
| E2guardian | 5.5.9r | Web content filtering proxy (client facing, port 8080) |
| Squid | 6.x | Caching upstream (loopback only, 127.0.0.1:3128) |
| e2guardian.service | systemd | Runs the filtering proxy in the foreground under supervision |
| squid.service | systemd | Runs the caching upstream, cache initialised on first start |
| e2guardian-firewall.service | systemd | Applies the nftables source allowlist, fail closed |
| squid-e2guardian-firstboot.service | systemd | Per VM proxy facts and loop prevention address |
| nftables | Ubuntu 24.04 | Enforces the proxy source allowlist |
| Ubuntu | 24.04 LTS | Base operating system |
Filesystem Layout
| Path | Purpose |
|---|---|
/etc/e2guardian/e2guardian.conf |
Main e2guardian configuration (including proxyip to Squid) |
/etc/e2guardian/e2guardianf1.conf |
Filter group configuration and list directory definition |
/etc/e2guardian/lists/example.group/ |
The filter group's lists, including localbannedsitelist |
/etc/e2guardian/cloudimg-firewall.nft |
The nftables source allowlist |
/etc/e2guardian/private/ |
Empty by design; no CA certificate or key is ever created |
/etc/squid/squid.conf |
Squid configuration (loopback only, secure by default) |
/var/spool/squid/ |
Squid on disk cache |
/var/log/e2guardian/access.log |
Structured filtering access log |
/var/log/squid/access.log |
Structured cache access log |
/root/squid-e2guardian-info.txt |
Root only per VM instance note |
/usr/share/doc/cloudimg/ |
GPL-2 licences, written offer and corresponding source |
Persistence, first boot and updates
On each VM's first boot, squid-e2guardian-firstboot.service resolves this machine's own address and records it as a checkip entry so a client that points the proxy back at itself cannot spin a request loop, writes fresh empty access logs so no build time browsing history is ever present, writes the instance note and MOTD, and re-asserts that no CA material exists. It then marks itself complete and does not run again. Squid initialises its cache directory automatically on first start.
Ubuntu security updates are applied automatically by unattended upgrades. E2guardian was built from source at image build time and Squid is the archive package, so new upstream releases arrive through a refreshed cloudimg image rather than needing manual work.
Security Recommendations
- Never expose port
8080to the internet. Keep the NSG rule scoped to your client subnet. The on host allowlist is a safety net, not a substitute for the NSG. - Leave Squid on loopback. It is an internal component; a routable Squid would let clients bypass the content filter entirely.
- Keep the two client facing layers in agreement. If you widen the
e2g_allowednftables set, widen the NSG rule to match, and no further. - Leave TLS interception off unless you have a specific mandate for it. Enabling it means generating and distributing a CA private key, and every client that trusts it is exposed if that key leaks. Hostname filtering covers most policy needs without that risk.
- Restrict admin SSH to your management network, and keep it key based.
- Forward the access logs to a SIEM if browsing records are subject to retention requirements.
- Review your block lists on a schedule, and remove the shipped
example.orgdemo entry once your own policy is in place.
Support
24/7 cloudimg support with a 24h response SLA is included with this image. Contact support@cloudimg.co.uk.
E2guardian and Squid are each distributed under the GNU General Public License version 2. The complete corresponding source for the e2guardian binaries in this image ships on the image at /usr/share/doc/cloudimg/e2guardian-5.5.9r-complete-source.tar.gz, alongside the licence text and a written offer valid for three years; Squid is the unmodified Ubuntu archive package and its source is available from the Ubuntu archive. cloudimg is not affiliated with or endorsed by the E2guardian project, the Squid project or the Squid Software Foundation. "E2guardian" and "Squid" are used nominatively to identify the software; all product and company names are trademarks of their respective holders.