What you will set up
When several servers install the same updates, each of them downloads the same packages from the internet. A package cache downloads each package once and hands out its copy after that. Unlike a full mirror, it only keeps what your servers actually install, so a few gigabytes are usually enough, and there is nothing to sync.
- apt-cacher-ng caches for Debian and Ubuntu.
- pacoloco caches for Arch Linux, CachyOS and EndeavourOS.
Both sit behind Caddy from the Podman guide, which gives them HTTPS and only lets your own servers in. Set up either or both.
Every step below was run on a Melonslab server with Debian 13. Debian 13 and Ubuntu 24.04 installed packages through apt-cacher-ng, and Arch Linux installed packages from its own, CachyOS's and EndeavourOS's repositories through pacoloco. The second installs came from the cache, and requests from any other address were refused.
Before you start
You need:
- a server set up as in the Podman guide, with Caddy running;
- a name for each cache, such as
apt-cache.example.comandpacman-cache.example.com, with A and AAAA records pointing at the server; - the IPv4 and IPv6 addresses of the servers that will use the cache. The examples use
198.51.100.20and2001:db8:2::20.
List both addresses of every server: a server that has IPv6 connects over IPv6, and is refused if only its IPv4 address is on the list.
Debian and Ubuntu: apt-cacher-ng
1. Install it
As root:
apt update
apt install -y apt-cacher-ng
If apt asks whether to allow HTTP tunnels, keep the answer No. Debian's package runs apt-cacher-ng as its own user and keeps the cache in /var/cache/apt-cacher-ng.
2. Listen on the server only
Out of the box it listens on every address, which would make it a cache for anyone on the internet. Create /etc/apt-cacher-ng/local.conf:
# Only Caddy, on this server, can reach the cache.
BindAddress: 127.0.0.1 ::1
systemctl restart apt-cacher-ng
3. Put Caddy in front
Switch to Caddy's user with machinectl shell caddy@, and add this block to the end of ~/Caddyfile, with your own servers' addresses:
apt-cache.example.com {
@servers remote_ip 198.51.100.20 2001:db8:2::20
handle @servers {
reverse_proxy 127.0.0.1:3142
}
respond 403
}
Restart Caddy with systemctl --user restart caddy. Caddy sees each visitor's real address, so the servers on the list get the cache and everyone else gets "403 Forbidden".
4. Point your servers at it
On each server that uses the cache, as root:
sed -i 's#http://#https://apt-cache.example.com/#' /etc/apt/sources.list
apt update
That turns a line such as deb http://ftp.debian.org/debian trixie main into deb https://apt-cache.example.com/ftp.debian.org/debian trixie main: apt asks the cache, and the cache fetches from the same place as before. Servers set up with the newer format keep their sources in /etc/apt/sources.list.d/debian.sources, or ubuntu.sources on Ubuntu; run the same sed on that file instead.
apt still checks the signature on every package, so the cache cannot change what your servers install.
Arch, CachyOS and EndeavourOS: pacoloco
1. Create the user
As root:
useradd -m -s /bin/bash pacoloco
loginctl enable-linger pacoloco
machinectl shell pacoloco@
2. Describe the cache
Create ~/pacoloco.yaml, with the repositories you use:
cache_dir: /var/cache/pacoloco
# Remove packages nobody has asked for in 30 days
purge_files_after: 2592000
repos:
archlinux:
urls:
- https://mirror.accum.se/mirror/archlinux
endeavouros:
urls:
- https://mirror.alpix.eu/endeavouros/repo
cachyos:
urls:
- https://mirror.zyner.org/cachyos/repo
Without purge_files_after, pacoloco never removes anything. Each repository entry names an upstream mirror. These are ones close to Sweden; any mirror from the distribution's own mirror list works.
Create ~/.config/containers/systemd/pacoloco.container, after mkdir -p ~/.config/containers/systemd:
[Unit]
Description=pacoloco, a package cache for pacman
[Container]
ContainerName=pacoloco
Image=ghcr.io/anatol/pacoloco:1.9
Volume=%h/pacoloco.yaml:/etc/pacoloco.yaml:ro
# :U gives the volume to the user pacoloco runs as inside the container
Volume=pacoloco-cache:/var/cache/pacoloco:U
# Only Caddy, on this server, can reach it: the port is not open to the internet.
PublishPort=127.0.0.1:8087:9129
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
The 1.9 tag is pacoloco's current release. Its latest tag follows unreleased code, so move to a newer release by changing the tag yourself.
systemctl --user daemon-reload
systemctl --user start pacoloco
systemctl --user enable --now podman-auto-update.timer
3. Put Caddy in front
As caddy, add this block to ~/Caddyfile, with your own servers' addresses, and restart Caddy:
pacman-cache.example.com {
@servers remote_ip 198.51.100.20 2001:db8:2::20
handle @servers {
reverse_proxy 127.0.0.1:8087
}
respond 403
}
4. Point your machines at it
Put the cache first in each mirror list, above the other Server lines:
| Distribution | File | Line |
|---|---|---|
| Arch Linux | /etc/pacman.d/mirrorlist | Server = https://pacman-cache.example.com/repo/archlinux/$repo/os/$arch |
| CachyOS | /etc/pacman.d/cachyos-mirrorlist | Server = https://pacman-cache.example.com/repo/cachyos/$arch/$repo |
| EndeavourOS | /etc/pacman.d/endeavouros-mirrorlist | Server = https://pacman-cache.example.com/repo/endeavouros/$repo/$arch |
CachyOS's -v3 and -v4 mirror lists use $arch_v3 or $arch_v4 in place of $arch; add the same line there with that change. pacoloco does not check package signatures itself, but pacman does, so the cache cannot change what you install.
Troubleshooting
apt or pacman gets "403 Forbidden". The machine's address is not on the list in the Caddy block. Check both of its addresses with curl -4 ifconfig.co and curl -6 ifconfig.co on that machine.
The cache fills the disk. apt-cacher-ng cleans out packages that are no longer in any repository every night. pacoloco removes packages nobody has asked for in 30 days, as set with purge_files_after; lower it if the disk is small.
Alpine or Rocky Linux. Neither project documents a package cache like these, so this guide does not cover them.