Squid is a forward proxy: clients send their web requests to it, Squid fetches the content and keeps a copy so the next client that asks for the same object gets it from the local cache. It is commonly used to cache package downloads for a fleet of servers, to give an office a single controlled exit to the internet, or to restrict which sites a network can reach. In this tutorial you will install Squid on Ubuntu 24.04, give it a disk cache with sensible refresh rules, lock it down with an IP allowlist and password authentication, and measure how well the cache performs.
Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS with at least 2 GB of RAM, for example a CubePath VPS, and a non-root user with
sudoprivileges. - Free disk space for the cache. This guide uses 10 GB; size it to your traffic.
- The public IP address or network range of the clients that will use the proxy, referred to as
your_client_ip.
WarningA proxy that accepts requests from anyone is an open proxy. It will be found within hours and abused for spam and attacks, and your IP will end up on blocklists. Keep both the IP allowlist and authentication from this guide in place.
Step 1 - Installing Squid
Squid is in the Ubuntu repositories:
sudo apt update
sudo apt install squid apache2-utils
apache2-utils provides htpasswd, which you will use in Step 4 to create proxy users. The service starts automatically. Confirm that it is running and listening on its default port, 3128:
systemctl status squid --no-pager
sudo ss -tlnp | grep 3128
● squid.service - Squid Web Proxy Server
Loaded: loaded (/usr/lib/systemd/system/squid.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-24 10:12:03 UTC; 25s ago
...
LISTEN 0 256 *:3128 *:* users:(("squid",pid=2143,fd=11))
Step 2 - Understanding where your configuration goes
The main file, /etc/squid/squid.conf, is several thousand lines of commented defaults. Rather than editing it, put your settings in a separate file under /etc/squid/conf.d/. Ubuntu's squid.conf already includes that directory, at the exact point where local access rules belong:
grep -n -E '^include|^http_access' /etc/squid/squid.conf
...:http_access deny !Safe_ports
...:http_access deny CONNECT !SSL_ports
...:http_access allow localhost manager
...:http_access deny manager
...:http_access allow localhost
...:http_access deny to_localhost
...:http_access deny to_linklocal
...:include /etc/squid/conf.d/*.conf
...:http_access deny all
Squid evaluates http_access lines from top to bottom and stops at the first match. The defaults above block unusual ports, allow the server itself and stop clients from reaching the server's own loopback and link-local addresses through the proxy. Then your rules from conf.d run, and anything they do not allow reaches the final deny all. Keep that order in mind when adding rules.
Step 3 - Configuring the cache
By default Squid on Ubuntu only caches in memory. Create your configuration file and add a disk cache:
sudo nano /etc/squid/conf.d/local.conf
# Memory cache for small, frequently requested objects
cache_mem 256 MB
maximum_object_size_in_memory 1 MB
# Disk cache: must come after maximum_object_size
maximum_object_size 512 MB
cache_dir ufs /var/spool/squid 10000 16 256
# Keep the objects that save the most bandwidth
cache_replacement_policy heap LFUDA
# Package files never change once published
refresh_pattern -i \.(deb|rpm|tar\.gz|tar\.xz|zip)$ 10080 90% 43200
refresh_pattern -i (/Packages|/Release|/InRelease)(\.gz|\.xz)?$ 0 20% 60
Each directive has a purpose:
cache_memis the RAM used for hot objects, in addition to Squid's own memory. Keep it well below the server's free RAM.cache_dir ufs /var/spool/squid 10000 16 256creates a 10,000 MB disk cache with 16 first-level and 256 second-level directories.maximum_object_sizelets large downloads such as packages and ISO images be cached. It must appear beforecache_dir, or the cache directory keeps the 4 MB default.refresh_patternlines set how long, in minutes, an object without explicit expiry headers counts as fresh (minimum, percentage of its age, maximum). The first line keeps package files for a long time; the second keeps repository index files short so clients see new packages quickly. Ubuntu's default patterns insquid.confstill apply to everything else.
Restart Squid. The systemd unit runs squid -z before every start, which creates any missing cache directories:
sudo systemctl restart squid
Verify that the directories exist:
sudo ls /var/spool/squid
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F swap.state
Step 4 - Restricting access by IP and password
Create the first proxy user. The -c flag creates the file, so leave it out when adding more users:
sudo htpasswd -c /etc/squid/passwords your_user
Squid runs as the proxy user on Ubuntu, so it needs read access to the file, while other users should not see it:
sudo chown root:proxy /etc/squid/passwords
sudo chmod 640 /etc/squid/passwords
Add the access rules to the end of /etc/squid/conf.d/local.conf:
sudo nano /etc/squid/conf.d/local.conf
# Clients allowed to use the proxy
acl trusted_clients src your_client_ip
# Username and password authentication
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords
auth_param basic children 5
auth_param basic realm Proxy
auth_param basic credentialsttl 2 hours
acl authenticated proxy_auth REQUIRED
# Optional: block some domains for everyone
acl blocked_sites dstdomain .example-blocked.com
http_access deny blocked_sites
# Allow trusted IPs that also authenticate
http_access allow trusted_clients authenticated
# Do not reveal client IPs to the sites they visit
forwarded_for delete
Replace your_client_ip with an address or CIDR range such as 203.0.113.0/24; you can repeat the acl trusted_clients src line to add more. Both conditions in the http_access allow line must match, so a request from an allowed IP without a password gets a 407 asking for credentials, and a request from any other IP is denied.
Validate the configuration before applying it. squid -k parse prints any errors and the line they are on:
sudo squid -k parse 2>&1 | grep -iE 'error|warning' || echo "Configuration OK"
sudo systemctl reload squid
Finally, open the port in the firewall only for your clients:
sudo ufw allow OpenSSH
sudo ufw allow from your_client_ip to any port 3128 proto tcp
sudo ufw enable
Step 5 - Testing the proxy from a client
From an allowed client, send a request through the proxy without credentials. Squid should refuse it:
curl -sI -x http://your_server_ip:3128 http://example.com | head -n 1
HTTP/1.1 407 Proxy Authentication Required
Now authenticate. Replace your_password with the password you set with htpasswd:
curl -sI -x http://your_user:your_password@your_server_ip:3128 http://example.com | head -n 1
HTTP/1.1 200 OK
HTTPS works too. For HTTPS, the client opens an encrypted tunnel through Squid with the CONNECT method, so Squid can allow or deny the destination but cannot see or cache the content:
curl -sI -x http://your_user:your_password@your_server_ip:3128 https://example.com | head -n 3
HTTP/1.1 200 Connection established
HTTP/2 200
To make command-line tools on a Linux client use the proxy, export the standard variables in the shell:
export http_proxy="http://your_user:your_password@your_server_ip:3128"
export https_proxy="$http_proxy"
For APT, create /etc/apt/apt.conf.d/95proxy on the client with the line Acquire::http::Proxy "http://your_user:your_password@your_server_ip:3128";. Package downloads from Ubuntu mirrors use plain HTTP, so they are cached.
NoteBasic authentication sends the password to the proxy encoded but not encrypted. Use it only over networks you trust, or reach the proxy through a VPN such as WireGuard.
Step 6 - Checking the cache hit ratio
Download the same package file twice through the proxy from a client:
URL=http://archive.ubuntu.com/ubuntu/pool/main/h/hello/hello_2.10-3build2_amd64.deb
curl -s -o /dev/null -x http://your_user:your_password@your_server_ip:3128 "$URL"
curl -s -o /dev/null -x http://your_user:your_password@your_server_ip:3128 "$URL"
If that exact file has been removed from the mirror, pick any .deb under http://archive.ubuntu.com/ubuntu/pool/. On the server, look at the last lines of the access log:
sudo tail -n 2 /var/log/squid/access.log
1790244725.311 412 203.0.113.10 TCP_MISS/200 53218 GET http://archive.ubuntu.com/ubuntu/pool/main/h/hello/hello_2.10-3build2_amd64.deb your_user HIER_DIRECT/185.125.190.83 application/vnd.debian.binary-package
1790244727.902 1 203.0.113.10 TCP_MEM_HIT/200 53226 GET http://archive.ubuntu.com/ubuntu/pool/main/h/hello/hello_2.10-3build2_amd64.deb your_user HIER_NONE/- application/vnd.debian.binary-package
The first request is a TCP_MISS fetched from the mirror; the second is a TCP_MEM_HIT served from memory in 1 ms. Larger objects show TCP_HIT when they come from the disk cache. TCP_TUNNEL entries are HTTPS connections, which are never cached, and TCP_DENIED entries are refused requests.
For overall statistics, query Squid's cache manager. It is allowed only from localhost, so run this on the server:
curl -s http://localhost:3128/squid-internal-mgr/info | grep -E 'Hits as|Storage Swap size|Storage Mem size'
Hits as % of all requests: 5min: 38.2%, 60min: 31.7%
Hits as % of bytes sent: 5min: 61.4%, 60min: 55.0%
Storage Swap size: 1843212 KB
Storage Mem size: 118340 KB
The byte hit ratio is the one that tells you how much bandwidth the cache saves.
Troubleshooting
Squid does not start after a change. Run sudo squid -k parse to find the faulty line, and read sudo journalctl -u squid -n 50 and /var/log/squid/cache.log.
Clients get 403 Forbidden or TCP_DENIED/403. The client IP does not match trusted_clients. The access log shows the IP Squid sees; if clients are behind NAT, allow the public address of the NAT.
Clients get 407 even with the right password. Check the password file permissions with ls -l /etc/squid/passwords (group proxy, mode 640) and test the helper by hand: run /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords as root, type your_user your_password and press Enter. It answers OK or ERR. Press Ctrl+D to exit.
The cache never shows hits. Most traffic today is HTTPS, which Squid tunnels without caching. Hits come from plain HTTP content such as package mirrors. Also check that the objects are not larger than maximum_object_size.
Conclusion
You now have a Squid proxy that caches HTTP content on disk and in memory, only accepts authenticated clients from known addresses, and reports how much traffic it saves. As next steps, point your servers' APT configuration at the proxy to speed up updates, rotate and archive /var/log/squid/access.log for auditing, and grow cache_dir if the byte hit ratio shows the cache filling up.
