Skip to content

Scheduling Home Wi-Fi With Squid Delay Pools in Docker

Adityo Guni Waluyo

A time-based bandwidth cap on a home LAN using Squid delay pools: bytes vs bits, the acl time trap, container TZ, and a second unlimited pool.

Last week I set up a bandwidth cap on my home network: a Squid proxy in Docker, on a schedule: full speed at night, throttled during work hours. The plan was simple, but my first two attempts missed: the speedtest capped at the wrong number, and the night schedule I wrote fired in broad daylight . Two classic traps got me: units (bytes vs bits) and container timezone.

How Delay Pools Work: Think Buckets

The easiest way to understand Squid delay pools is to picture buckets of bandwidth tokens: each bucket refills at a fixed rate every second, passing data drinks from the bucket, and an empty bucket means the transfer waits [1]. A single pool can hold several bucket levels at once (an aggregate for all clients, per-network, down to per-host individual)and any level you don't need can be disabled [1]. For a small LAN like mine, class 3 fits best because it has all three levels [1].

The practical question: what number goes in the bucket? This is where many people (me included) misread the docs, because delay_parameters is measured in bytes per second, not bits [2]. Link speeds are quoted in bits, so a 40 Mbps cap is 5 million bytes per second (the 5000000 in the config is not a typo, it's the correct conversion [2]. The parameter is a restore/maximum pair: restore is the refill rate, maximum is the bucket size, both in bytes [2]. The official wiki recommends a maximum of at least twice the restore so short bursts stay comfortable)a fresh page loads instantly instead of waiting for the bucket to drip [1].

Two limits to keep in mind: delay pools only throttle payload (TCP overhead, DNS queries and the like pass normally [1])and one big connection can drain a bucket and starve other connections [1]. The practical consequence: a single-connection speedtest will plateau at the cap, while tools opening many connections can exceed the paper number . That's my estimate from how buckets behave, not something I measured precisely.

The WIB Schedule: acl time, Container TZ, and a Second Pool

For scheduling, Squid has a time ACL written as acl aclname time [day-abbrevs] [h1:m1-h2:m2]: you can restrict it to specific days or let it apply daily [3]. One rigid rule only bites after you hit it: h1:m1 must be smaller than h2:m2 [3]. No wrapping past midnight. If my work hours are 09:00–21:00, no single ACL can cover the complement (21:00–09:00); the fix is not to force one pool but to add a second, deliberately unlimited pool with -1/-1 [1].

The config that finally works on my server:

acl klien src 192.168.x.x/24
acl jamkerja time 09:00-21:00
delay_pools 2
delay_class 1 3
delay_parameters 1 -1/-1 -1/-1 5000000/10000000
delay_access 1 allow jamkerja
delay_class 2 1
delay_parameters 2 -1/-1
delay_access 2 allow all

Pool 1 only applies when the workhours ACL matches. Being class 3, it has three bucket slots: I disable the aggregate and network ones and keep a per-client bucket of 5,000,000/10,000,000 bytes: a 40 Mbps cap with roughly two seconds of burst. Pool 2 catches everything outside 09:00–21:00 as a class 1 pool, the simplest kind: one aggregate bucket, set to unlimited [1]. The result is the static policy I wanted: outside work hours clients run at full speed with no extra config.

The sneakier third trap: the time ACL reads the local clock of the host running Squid, and in a container that defaults to UTC (so a 09:00 WIB schedule can be interpreted as 02:00 and the throttle fires at the wrong hours . The fix is one line: TZ=Asia/Jakarta in the container environment)another case of container isolation you only feel once the schedule is already wrong .

As a comparison, 3proxy offers bandlimin/bandlimout for static two-direction caps; simpler, but without time-based scheduling . For a "slow at work hours, free at night" policy I still need Squid delay pools, and on my box both run side by side: Squid for the schedule, 3proxy for the static rate .

Real-World Numbers and a Quick Checklist

On my own measurements, the 5,000,000/10,000,000-byte setting yielded roughly 39 Mbps down and 42 Mbps up per client: around the 40 Mbps cap, which is fair for payload-only shaping. These are numbers from my own network, not a general benchmark; yours will differ with network conditions .

If you want to copy this pattern, my checklist:

  • Write caps in bytes per second: Mbps ÷ 8 × 1,000,000 [2].
  • The container TZ must match the schedule's timezone; check quickly from the host with docker exec date .
  • A complementary schedule needs a second pool, because a time ACL cannot wrap midnight [3].
  • Use -1/-1 to disable buckets you don't use [1].
  • Run a speedtest twice (inside and outside the window)before trusting the numbers .

Sources

[1] https://wiki.squid-cache.org/Features/DelayPools

[2] https://www.squid-cache.org/Doc/config/delay_parameters/

[3] https://www.squid-cache.org/Doc/config/acl/

Sumber