How to Install and Configure Squid Proxy Server on CentOS Stream 9: Controlling Internet Access for Enterprise Networks with SELinux and firewalld

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Context: When Does a Business Need Squid Proxy?

I first got my hands on Squid back when I was a sysadmin at a logistics company. The IT team was getting complaints from HR that employees were watching YouTube during work hours, and the bandwidth drain was causing video calls to lag constantly. The solution was to set up a Squid Proxy — and it was done within a single morning.

Squid Proxy Server lets you do things a regular router simply cannot:

  • Domain-based access control: Block YouTube, Facebook, TikTok by list, or whitelist only the sites that are actually needed
  • Time-based control: Allow social media during lunch breaks, block it outside those hours
  • Content caching: Multiple machines downloading the same file — only one trip out to the Internet, saving significant bandwidth
  • Full audit logging: Know exactly which machine, which IP, visited which website, and at what time
  • Internal IP masking: Clients reach the Internet through the proxy IP, keeping your internal network topology hidden

My company still has a few servers running CentOS 7, and migrating them to AlmaLinux is a problem I’ve already tackled. But for low-traffic proxy servers, the team still opts for CentOS Stream 9 due to its clear support lifecycle and solid compatibility with the RHEL ecosystem. In this article, I’ll walk through setting up Squid from scratch, including handling SELinux and firewalld — the two things that give junior developers the most headaches when first approaching RHEL/CentOS.

Installing Squid on CentOS Stream 9

Step 1: Update the System and Install Squid

Start by updating the system, then install Squid from the default CentOS Stream 9 repository:

sudo dnf update -y
sudo dnf install squid -y

Verify the installed version:

squid -v

The output returns build information, including a line showing Squid Cache: Version 5.x.

Step 2: Start and Enable the Service

sudo systemctl enable squid --now
sudo systemctl status squid

If you see Active: active (running), you’re good to go. Squid listens on port 3128 by default.

Detailed Squid Configuration

Creating a Clean Configuration File

The default /etc/squid/squid.conf file has around ~7000 lines of comments. I usually back it up and rewrite a leaner config file for easier management:

sudo cp /etc/squid/squid.conf /etc/squid/squid.conf.backup
sudo nano /etc/squid/squid.conf

Paste in a standard enterprise configuration:

# ============================================================
# Squid Proxy - Enterprise Config
# CentOS Stream 9
# ============================================================

# Listening port
http_port 3128

# === ACL: Define local network ===
acl localnet src 192.168.0.0/16
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12

# Standard ACLs
acl SSL_ports port 443
acl Safe_ports port 80 443 8080 8443 21 70 210 280 488 591 777
acl CONNECT method CONNECT

# === ACL: Blocked domain list ===
acl blocked_sites dstdomain "/etc/squid/blocked_sites.txt"

# === Rules ===
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access deny blocked_sites
http_access allow localnet
http_access allow localhost
http_access deny all

# === Cache ===
cache_dir ufs /var/spool/squid 1024 16 256
maximum_object_size 50 MB
cache_mem 256 MB

# === Logging ===
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log

# Hide server information
via off
forwarded_for off
httpd_suppress_version_string on

Creating the Blocked Website List

sudo nano /etc/squid/blocked_sites.txt

Example content:

.youtube.com
.facebook.com
.tiktok.com
.netflix.com
.instagram.com

Note: The leading dot . before a domain blocks all subdomains as well — for example, .youtube.com also blocks www.youtube.com, m.youtube.com, and music.youtube.com.

Configuring Time-Based ACLs for Working Hours

A real-world scenario: allow social media during the lunch break (12:00–13:00), block it outside that window:

# Add to squid.conf (before the Rules section)

# Working hours: Monday-Friday, 8am-12pm and 1pm-6pm
acl working_hours time MTWHF 08:00-12:00
acl working_hours time MTWHF 13:00-18:00

# Lunch break
acl lunch_break time MTWHF 12:00-13:00

# Social media domains
acl social_media dstdomain .facebook.com .youtube.com .tiktok.com

# Applied rules:
http_access deny social_media working_hours
http_access allow social_media lunch_break localnet

Configuring SELinux for Squid

This is the part many people skip, then wonder why Squid won’t run even though the config looks correct. SELinux on CentOS Stream 9 is in Enforcing mode by default.

Check the current status:

getenforce
# Output: Enforcing

Squid needs some specific SELinux permissions. Port 3128 is already allowed by SELinux by default, but if you want to switch to a different port (e.g., 8080):

# Add port 8080 to the squid_port_t policy
sudo semanage port -a -t squid_port_t -p tcp 8080

# Verify
sudo semanage port -l | grep squid

If Squid needs to connect to non-standard ports (e.g., an upstream proxy), enable this boolean:

sudo setsebool -P squid_connect_any 1

When you encounter a permission denied error and aren’t sure what SELinux is blocking, here’s a quick debugging approach:

# View recent audit log denials
sudo ausearch -c 'squid' --raw | audit2allow -M my-squid

# Apply the generated policy
sudo semodule -i my-squid.pp

Configuring firewalld

Open port 3128 so clients on the internal network can connect to the proxy:

# Use the built-in squid service in firewalld (more convenient)
sudo firewall-cmd --zone=internal --add-service=squid --permanent

# Or open the port directly if you prefer
sudo firewall-cmd --zone=internal --add-port=3128/tcp --permanent

# Apply changes
sudo firewall-cmd --reload

# Check the internal zone
sudo firewall-cmd --zone=internal --list-all

Check which zone your internal network interface belongs to, and move it to the internal zone if needed:

# See which interface is in which zone
sudo firewall-cmd --get-active-zones

# Example: eth1 is the internal interface
sudo firewall-cmd --zone=internal --add-interface=eth1 --permanent
sudo firewall-cmd --reload

Validate Config and Restart Squid

Always check the syntax before restarting — a bad config can prevent the service from starting at all:

# Parse and validate the config
sudo squid -k parse

# No errors → restart
sudo systemctl restart squid

Testing and Monitoring

Test the Proxy from a Client

From a client machine on the internal network, use curl to test through the proxy (replace 192.168.1.100 with the actual IP of your Squid server):

# Test normal access
curl -x http://192.168.1.100:3128 http://example.com -I
# Expected: HTTP/1.1 200 OK

# Test blocked domain
curl -x http://192.168.1.100:3128 http://www.youtube.com -I
# Expected: HTTP/1.1 403 Forbidden

View Logs in Real Time

Squid’s access log records all activity — this is the primary source of information for security auditing:

sudo tail -f /var/log/squid/access.log

Each log line looks like this:

1720123456.789  1234 192.168.1.50 TCP_MISS/200 5432 GET http://example.com/ - DIRECT/93.184.216.34 text/html
  • 1720123456.789: Unix timestamp
  • 1234: Processing time (milliseconds)
  • 192.168.1.50: Client IP
  • TCP_MISS/200: Cache miss, server returned HTTP 200
  • GET http://example.com/: HTTP method and full URL

View Squid Statistics

# General overview statistics
sudo squidclient -h localhost mgr:info

# Cache hit rate
sudo squidclient -h localhost mgr:counters | grep -i hit

Script to Find the Top Most-Accessed Domains

Useful when you need to generate weekly reports for management:

#!/bin/bash
# Top 10 most accessed domains in the current log
cat /var/log/squid/access.log | \
  awk '{print $7}' | \
  sed 's|http[s]*://||' | \
  cut -d'/' -f1 | \
  sort | uniq -c | \
  sort -rn | \
  head -10

Configuring Log Rotation

In a high-traffic environment, Squid logs can balloon to several gigabytes within days. Configure logrotate to clean them up automatically:

sudo nano /etc/logrotate.d/squid
/var/log/squid/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        /usr/sbin/squid -k rotate
    endscript
}

Troubleshooting Common Issues

Squid won’t start — permission error:

# Check if SELinux is blocking anything
sudo ausearch -m avc -ts recent | grep squid

# Quick debug: temporarily set permissive mode (for testing ONLY, never use in production)
sudo setenforce 0
sudo systemctl restart squid
# If it works → the issue is SELinux → use audit2allow to fix it properly
sudo setenforce 1

Client cannot connect through the proxy:

# Which port is Squid listening on?
sudo ss -tlnp | grep squid

# Is the firewall blocking anything?
sudo firewall-cmd --list-all --zone=internal

# Try telnet from the client to the proxy
telnet 192.168.1.100 3128

Once Squid is up and running stably, the next step I usually take is switching to a transparent proxy — clients don’t need to configure a proxy manually, as all HTTP traffic is automatically routed through Squid via iptables redirect. But that’s a more complex topic that also requires SSL bump to inspect HTTPS traffic — I’ll save it for another article.

Share: