Configure Your Own Squid Proxy Server: Secure Step-by-Step Guide
A forward proxy gives approved clients controlled outbound web access through a managed server. Squid can enforce source networks, user authentication, destination and port policy, logging, caching decisions, and change controls. This guide builds an explicit Squid forward proxy from a clean Linux host, tests HTTP and HTTPS tunneling, configures clients, and provides troubleshooting and rollback procedures.
This procedure targets a current distribution-packaged Squid release. Package paths and compiled features differ between RHEL-family and Debian-family systems, so every helper and configuration path is discovered on the installed host before it is used.
Understand what this proxy does
| Mode | Client behavior | Use | Boundary |
|---|---|---|---|
| Explicit forward proxy | Client is configured with proxy host and port | This guide; controlled outbound HTTP and HTTPS CONNECT | Does not decrypt HTTPS content by default. |
| Intercepting proxy | Network redirects traffic without normal proxy configuration | Special network designs | Authentication is disabled on intercept/tproxy ports and HTTPS interception has major security, privacy, legal, and application risks. |
| Reverse proxy | Internet user connects to a published application endpoint | Protecting and routing inbound application traffic | Use a reverse-proxy design; it is not the configuration in this guide. |
| SOCKS proxy | SOCKS-aware application connects through a general relay | Protocols beyond HTTP proxy behavior | Squid is primarily an HTTP forward proxy, not a general SOCKS server. |
A proxy can support corporate Internet policy, restricted cloud-server egress, central audit, bandwidth controls, and caching where responses permit it. It does not automatically provide anonymity, malware protection, end-to-end privacy, or safe censorship bypass. The destination still sees the proxy address, while DNS, authentication, cookies, browser fingerprints, account activity, and proxy logs may identify the user.
Plan the deployment before installation
The worked example uses the private proxy address 10.20.30.10, approved client range 10.20.30.0/24, and TCP port 3128. Replace them with verified inventory. Do not expose an open proxy to the internet.
| Decision | Record | Reason |
|---|---|---|
| Clients | Source subnets, owners, operating systems, applications | Defines who can reach and use the proxy. |
| Destinations | Allowed ports, domains, exceptions, approval owner | Prevents Squid becoming a general TCP relay. |
| Identity | Network-only, local Basic auth, LDAP/Kerberos, or workload identity | Controls attribution and credential risk. |
| Availability | Single host or redundant service, fail-open/fail-closed expectation | Clients may lose web access when the proxy is unavailable. |
| Logs | Purpose, retention, access, redaction, time synchronization | URLs and usernames can contain sensitive information. |
| Cache | Disabled or controlled; disk capacity and privacy policy | Most HTTPS is tunneled and many responses prohibit shared caching. |
Step 1: inspect the host and preserve rollback
date -u
hostnamectl
cat /etc/os-release
ip -brief address
ip route
ss -lntup
systemctl --failed
sudo nft list ruleset
sudo firewall-cmd --list-all 2>/dev/null || true
sudo ufw status verbose 2>/dev/null || true
Confirm console or out-of-band access and record the private listening interface. If Squid is already installed, back up only paths that exist:
sudo install -d -m 0700 /root/squid-change-backup
for proxy_path in /etc/squid/squid.conf /etc/squid/conf.d \
/etc/squid/passwords /etc/squid/squid_passwd; do
if sudo test -e "$proxy_path"; then
sudo cp -a -- "$proxy_path" /root/squid-change-backup/
fi
done
sudo sh -c 'nft list ruleset > /root/squid-change-backup/nftables-before.txt'
sudo find /root/squid-change-backup -type f -exec sha256sum {} +
The short command cp /etc/squid/squid.conf /etc/squid/org_squid.conf also creates a configuration copy, but it overwrites an existing org_squid.conf, does not preserve all metadata, and does not protect the backup directory. If it appears in an operating procedure, first verify that the destination does not exist. The protected, timestamped change-backup method above is the safer replacement.
Step 2: install Squid and authentication tools
RHEL, Rocky Linux, AlmaLinux, or Fedora
sudo dnf install -y squid httpd-tools
rpm -q squid httpd-tools
rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{SIGPGP:pgpsig}\n' \
squid httpd-tools
squid -v
rpm -ql squid | grep -E '/basic_ncsa_auth$|/squid\.conf$'
Ubuntu or Debian
sudo apt update
sudo apt install -y squid apache2-utils
dpkg-query -W squid apache2-utils
squid -v
dpkg -L squid | grep -E '/basic_ncsa_auth$|/squid\.conf$'
Do not guess the authentication-helper path. Common package paths are /usr/lib64/squid/basic_ncsa_auth on RHEL-family systems and /usr/lib/squid/basic_ncsa_auth on Debian-family systems, but the installed package list is authoritative.
Step 3: create a proxy user without exposing the password
Basic proxy authentication is only an identity check; credentials are not protected from anyone able to observe an unencrypted client-to-proxy connection. Use it only on a trusted private network or inside a protected tunnel. For a larger organization, use an approved directory-backed authentication design.
# RHEL-family service group is commonly squid.
# Debian-family service group is commonly proxy.
if getent group squid >/dev/null; then
SQUID_GROUP=squid
elif getent group proxy >/dev/null; then
SQUID_GROUP=proxy
else
echo 'Squid service group was not found' >&2
exit 1
fi
sudo install -o root -g "$SQUID_GROUP" -m 0640 \
/dev/null /etc/squid/passwords
sudo htpasswd /etc/squid/passwords proxyadmin
sudo htpasswd -v /etc/squid/passwords proxyadmin
sudo stat -c '%U %G %a %n' /etc/squid/passwords
The interactive command prompts twice and does not place the password in shell history or the process list. Add another user with sudo htpasswd /etc/squid/passwords USERNAME; delete one with sudo htpasswd -D /etc/squid/passwords USERNAME.
Unsafe: htpasswd -bc /etc/squid/squid_passwd squidadmin your-password exposes the password in shell history, process inspection, terminal capture, and automation logs. The command is included because it is widely encountered, but do not use it with a real password. Use the interactive form above or protected secret automation.
Step 4: build a minimal authenticated configuration
First discover the exact helper:
AUTH_HELPER="$(find /usr/lib /usr/lib64 -type f \
-name basic_ncsa_auth -print -quit 2>/dev/null)"
test -n "$AUTH_HELPER"
test -x "$AUTH_HELPER"
printf 'Authentication helper: %s\n' "$AUTH_HELPER"
Create /tmp/squid.conf.candidate as an ordinary administrator file, then open it in an editor. Replace 10.20.30.10, 10.20.30.0/24, and /PATH/TO/basic_ncsa_auth with the values verified on this host:
install -m 0600 /dev/null /tmp/squid.conf.candidate
vi /tmp/squid.conf.candidate
Unsafe and invalid when copied literally: echo ” ... ” > squid.conf uses typographic quotation marks rather than shell quotes, silently replaces a file in the current directory, and does not install or validate /etc/squid/squid.conf. Use the candidate, parser, protected backup, and installation sequence in this guide.
# Explicit forward proxy listener on the private interface only.
http_port 10.20.30.10:3128
visible_hostname proxy.example.net
# Client, destination and method definitions.
acl approved_clients src 10.20.30.0/24
acl localhost src 127.0.0.1/32 ::1
acl to_localhost dst 127.0.0.0/8 0.0.0.0/32 ::1
acl to_linklocal dst 169.254.0.0/16 fe80::/10
acl manager proto cache_object
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl CONNECT method CONNECT
# Local password-file authentication.
auth_param basic program /PATH/TO/basic_ncsa_auth /etc/squid/passwords
auth_param basic children 5 startup=1 idle=1
auth_param basic realm Controlled Web Proxy
auth_param basic credentialsttl 5 minutes
acl authenticated proxy_auth REQUIRED
# Rule order matters: safety denials, manager protection, allow, final deny.
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access deny to_localhost
http_access deny to_linklocal
http_access allow approved_clients authenticated
http_access deny all
# Start as a forwarding-only service. Enable caching only by policy.
cache deny all
# Do not enable ICP unless this host participates in a designed cache hierarchy.
icp_port 0
# Preserve normal forwarding behavior unless privacy policy requires a change.
forwarded_for on
# Packaged paths may differ; retain the package default when appropriate.
coredump_dir /var/spool/squid
The final http_access deny all is intentional. Squid evaluates rules in order; a broad allow placed before safety rules can turn the service into a relay. Binding to the private address plus firewall and ACL controls provides independent layers.
Account for additional Safe_ports entries
Some configurations also contain the following port declarations:
acl Safe_ports port 21 # FTP control
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # FileMaker
acl Safe_ports port 777 # multiling HTTP
acl Safe_ports port 1025-65535 # broad high-port range
A Safe_ports line does not open the host firewall, but it expands the destination ports that an allowed proxy request may reach. Add only a port required by an approved application and test it. The range 1025-65535 is Unsafe for a general proxy because it permits HTTP proxy requests to a very large destination-port range. FTP proxy behavior also depends on the installed Squid version and client workflow; do not add port 21 merely because the line is familiar. Keep SSL_ports limited to approved CONNECT destinations, normally 443, unless a documented application needs another TLS port.
The ACL name is local policy, not a reserved value. These two pairs perform the same authentication check when referenced in the correct access rule:
acl ncsa_users proxy_auth REQUIRED
# or
acl authenticated proxy_auth REQUIRED
Do not place http_access allow ncsa_users before deny !Safe_ports, deny CONNECT !SSL_ports, manager protection, localhost/link-local denial, and source-network restrictions. The first matching access rule wins, so an early authentication-only allow bypasses all later safety decisions.
Step 5: insert the discovered helper and validate
test -f /tmp/squid.conf.candidate
sudo cp -a /etc/squid/squid.conf \
/root/squid-change-backup/squid.conf-before-install
sudo sed -i "s|/PATH/TO/basic_ncsa_auth|$AUTH_HELPER|" \
/tmp/squid.conf.candidate
grep -n '^auth_param basic program' /tmp/squid.conf.candidate
# Parse the candidate before replacing the working configuration.
sudo squid -f /tmp/squid.conf.candidate -k parse
sudo install -o root -g root -m 0644 \
/tmp/squid.conf.candidate /etc/squid/squid.conf
sudo squid -k parse
A silent successful return from squid -k parse means Squid understood the syntax; it does not prove that the policy is correct. Inspect the effective listening address, ACL order, user-file permission, firewall, and test results separately.
Step 6: restrict the host and cloud firewall
Allow TCP 3128 only from approved client networks. Do not open it to 0.0.0.0/0 or ::/0. Apply matching restrictions in cloud security groups, network firewalls, routers, and host firewall.
firewalld example
sudo firewall-cmd --permanent --new-zone=proxy-clients
sudo firewall-cmd --permanent --zone=proxy-clients \
--add-source=10.20.30.0/24
sudo firewall-cmd --permanent --zone=proxy-clients \
--add-port=3128/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=proxy-clients --list-all
UFW example
sudo ufw allow from 10.20.30.0/24 to any port 3128 proto tcp
sudo ufw status numbered
Unsafe: firewall-cmd --add-port=3128/tcp in a broadly sourced zone or ufw allow 3128/tcp may make the proxy internet-reachable. Pair every rule with the verified source range and confirm external denial.
Step 7: start the service and verify the listener
sudo systemctl enable --now squid
sudo systemctl status squid --no-pager
sudo journalctl -u squid -n 100 --no-pager
sudo ss -lntp | grep ':3128'
sudo squid -k check
The listener should show the intended private address, not every public interface. If the configured private address is not assigned to the host, Squid will fail to bind; correct the inventory rather than changing it to a public wildcard.
Step 8: test denial and authenticated access
Run these from an approved client. The first request should return proxy authentication required. The next two should succeed after an interactive password prompt:
curl -I --proxy http://10.20.30.10:3128 http://example.com/
curl -I --proxy http://10.20.30.10:3128 \
--proxy-user proxyadmin http://example.com/
curl -I --proxy http://10.20.30.10:3128 \
--proxy-user proxyadmin https://example.com/
curl --proxy-user proxyadmin prompts for the password. Do not place proxyadmin:PASSWORD in the command, URL, shell history, process list, or shared script.
Also test from an unapproved source and confirm connection denial at the network layer. Test a disallowed CONNECT port:
curl -v --proxy http://10.20.30.10:3128 \
--proxy-user proxyadmin https://example.com:8443/
Step 9: configure common clients
Temporary shell variables
export http_proxy=http://10.20.30.10:3128
export https_proxy=http://10.20.30.10:3128
export no_proxy=localhost,127.0.0.1,::1,.internal.example
curl --proxy-user proxyadmin https://example.com/
unset http_proxy https_proxy no_proxy
Applications differ on uppercase/lowercase variables and authentication. Do not put a proxy password in a world-readable shell profile.
APT
sudo install -o root -g root -m 0644 /dev/null \
/etc/apt/apt.conf.d/80proxy
sudoedit /etc/apt/apt.conf.d/80proxy
Acquire::http::Proxy "http://10.20.30.10:3128/";
Acquire::https::Proxy "http://10.20.30.10:3128/";
DNF
sudoedit /etc/dnf/dnf.conf
proxy=http://10.20.30.10:3128
For unattended package managers, prefer source-network authorization, workload identity, or a root-readable credential mechanism supported by that application. Storing Basic credentials in configuration creates a secret-distribution problem.
systemd service
sudo systemctl edit example-service
[Service]
Environment="HTTP_PROXY=http://10.20.30.10:3128"
Environment="HTTPS_PROXY=http://10.20.30.10:3128"
Environment="NO_PROXY=localhost,127.0.0.1,::1,.internal.example"
sudo systemctl daemon-reload
sudo systemctl restart example-service
sudo systemctl show example-service -p Environment
Do not place credentials in a world-readable unit or display them with systemctl show. Use the service's approved credential facility or network-based policy.
Step 10: add destination controls
An allowlist is appropriate for restricted servers that need only approved update, API, or repository destinations. Domain matching is not a substitute for authenticating the remote TLS service, and CDNs may require several documented domains.
# Put one domain per line in /etc/squid/allowed-domains.txt:
# .example.com
# packages.example.net
acl allowed_domains dstdomain "/etc/squid/allowed-domains.txt"
# Place this before the authenticated general allow rule.
http_access allow approved_clients authenticated allowed_domains
http_access deny approved_clients
If the service is allowlist-only, remove the general http_access allow approved_clients authenticated rule. Validate and reconfigure:
sudo squid -k parse
sudo squid -k reconfigure
sudo journalctl -u squid --since '-5 minutes' --no-pager
Cache, privacy, headers, and ICP options
| Directive or feature | Current use | Recommended decision |
|---|---|---|
cache deny all | Prevents serving cached hits and storing misses for matching requests | Good starting point for an access-control proxy. |
refresh_pattern | Controls freshness when caching is enabled | Not useful when all caching is denied. If enabled, respect origin cache controls and start with packaged defaults. |
icp_port 3130 | Enables UDP ICP for neighbor caches | Leave disabled with icp_port 0 unless a designed cache hierarchy requires it. |
forwarded_for off | Replaces client address information in the forwarded header with an unknown value | Does not create anonymity. Choose a value through privacy, troubleshooting, and upstream-policy review. |
request_header_access | Allows targeted request-header policy | Do not deny every header not on a static list; modern applications can break and credentials/cookies are not made safe merely by forwarding them. |
coredump_dir | Selects the directory used for process core dumps and changes dumpability behavior | Use the packaged directory and protect core files because they may contain sensitive process data. |
hierarchy_stoplist | Removed directive that bypassed neighbor-cache queries for matching URLs | Do not copy this directive into a current configuration. With no cache peers, no replacement is needed. In a designed peer hierarchy, express routing through current cache_peer, always_direct, and never_direct ACL policy. |
Common refresh rules, when caching is intentionally enabled, include:
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern . 0 20% 4320
The minimum value 0 reduces the chance of treating dynamic content without explicit expiry as fresh. Do not use options that ignore no-store, private, reload, or origin expiry controls without a documented standards and application review.
refresh_pattern ^gopher: 1440 0% 1440 may be found in broader templates, but it is omitted from this current web-proxy configuration because Gopher is not part of the intended HTTP/HTTPS service. A refresh rule is also inactive for requests matched by cache deny all.
A blanket block like the following is Unsafe for a general proxy because it can remove extension headers required by authentication, tracing, APIs, WebSockets, content negotiation, and newer standards:
request_header_access Allow allow all
request_header_access Authorization allow all
request_header_access Proxy-Authorization allow all
request_header_access Proxy-Authenticate allow all
request_header_access Cache-Control allow all
request_header_access Content-Encoding allow all
request_header_access Content-Length allow all
request_header_access Content-Type allow all
request_header_access Date allow all
request_header_access Expires allow all
request_header_access Host allow all
request_header_access If-Modified-Since allow all
request_header_access Last-Modified allow all
request_header_access Location allow all
request_header_access Pragma allow all
request_header_access Accept allow all
request_header_access Accept-Charset allow all
request_header_access Accept-Encoding allow all
request_header_access Accept-Language allow all
request_header_access Content-Language allow all
request_header_access Mime-Version allow all
request_header_access Retry-After allow all
request_header_access Title allow all
request_header_access Connection allow all
request_header_access Proxy-Connection allow all
request_header_access User-Agent allow all
request_header_access Cookie allow all
request_header_access All deny all
This list preserves selected known headers and removes every other outgoing request header. Squid documents that header removal violates HTTP standards, requires a compatible build option, and defaults to allowing headers without these rules. Keep normal headers by default. If a specific header must be removed, define that single policy, scope it to the intended sources/destinations, test representative applications, and document the owner.
Logging, monitoring, and capacity
sudo tail -F /var/log/squid/access.log /var/log/squid/cache.log
sudo journalctl -fu squid
sudo du -sh /var/log/squid /var/spool/squid 2>/dev/null
sudo df -hT /var/log/squid /var/spool/squid 2>/dev/null
sudo df -ih /var/log/squid /var/spool/squid 2>/dev/null
sudo ss -nt state established '( sport = :3128 )'
Monitor service state, authentication failures, denied requests, upstream DNS/connect/TLS errors, response codes, latency, concurrent connections, file descriptors, memory, CPU, log volume, disk bytes and inodes. Synchronize time. Restrict logs because URLs, usernames, query strings and destination hosts may be sensitive.
Troubleshooting workflow
| Symptom | Evidence | Likely action |
|---|---|---|
| Connection refused | ss -lntp, service log, route, firewall | Correct bind address, service state, or approved network path. |
| HTTP 407 | Client credential prompt and access log | Verify username, password file permissions, helper path, and ACL order. |
| HTTP 403 | TCP_DENIED log line and source address | Check client subnet, safe port, destination policy, and final deny. |
| HTTP works, HTTPS fails | CONNECT target/port and TLS error | Permit only required CONNECT ports; test DNS and upstream reachability. |
| One application fails | Required headers, proxy support, WebSocket behavior | Remove blanket header manipulation and test the application's documented proxy settings. |
| Slow service | CPU, memory, DNS latency, helper queue, connections, upstream | Fix the measured bottleneck; do not enable caching blindly. |
| Service will not reload | squid -k parse and cache.log | Correct the candidate first; restore the last known-good configuration. |
sudo squid -k parse
sudo systemctl status squid --no-pager
sudo journalctl -u squid -n 200 --no-pager
sudo tail -n 200 /var/log/squid/cache.log
sudo tail -n 200 /var/log/squid/access.log
getent hosts example.com
ip route get 93.184.216.34
sudo -u squid test -r /etc/squid/passwords 2>/dev/null || \
sudo -u proxy test -r /etc/squid/passwords
Safe change and rollback procedure
# Validate a proposed file before installation.
sudo squid -f /tmp/squid.conf.candidate -k parse
# Back up the working file, install the candidate, parse again, then reload.
sudo cp -a /etc/squid/squid.conf \
/root/squid-change-backup/squid.conf.last-known-good
sudo install -o root -g root -m 0644 \
/tmp/squid.conf.candidate /etc/squid/squid.conf
sudo squid -k parse
sudo squid -k reconfigure
sudo systemctl is-active squid
If acceptance tests fail, restore the last known-good file:
sudo install -o root -g root -m 0644 \
/root/squid-change-backup/squid.conf.last-known-good \
/etc/squid/squid.conf
sudo squid -k parse
sudo squid -k reconfigure
sudo systemctl status squid --no-pager
Unsafe: squid -k kill immediately kills Squid without graceful connection shutdown or normal log handling. It remains useful as a last-resort incident command, but use systemctl stop squid or squid -k shutdown for controlled service shutdown.
Production acceptance checklist
- Squid listens only on the intended private address and TCP port.
- Cloud, network, and host firewalls allow only approved client sources.
- Unsafe ports, unauthorized CONNECT destinations, manager access, localhost, link-local, and all unmatched traffic are denied.
- Authentication helper, password-file permissions, credential rotation, and non-plaintext secret storage are tested.
- HTTP, HTTPS CONNECT, unauthorized source, invalid credential, disallowed port, and destination-policy tests produce the expected results.
- Header, privacy, cache, ICP, logging, retention, monitoring, capacity, high availability, and outage behavior have named owners.
- The candidate-parse, reload, acceptance, and rollback procedures pass from a real approved client.


