Introduction
Some enterprises run private servers with no internet access but still need to update packages, so they build their own mirrors. Many HomeLab hobbyists do the same to get the best possible speed and lowest latency. This article documents building a Debian mirror end to end, as a reference for anyone who needs one.
Test Environment
root@Mirror:~# neowofetch
_,met$$$$$gg. root@Mirror
,g$$$$$$$$$$$$$P. -----------
,g$$P" """Y$$.". OS: Debian GNU/Linux 13.6 (trixie) x86_64
,$$P' `$$$. Host: KVM/QEMU (Standard PC (i440FX + PIIX, 1996) pc-i440fx-11.0)
',$$P ,ggs. `$$b: Kernel: 6.12.74+deb13+1-amd64
`d$$' ,$P"' . $$$ Uptime: 6 mins
$$P d$' , $$P Packages: 450 (dpkg)
$$: $$. - ,d$$' Shell: bash 5.2.37
$$; Y$b._ _,d$P' Terminal: /dev/pts/0
Y$$. `.`"Y$$$$P"' CPU: Intel Xeon E5-2696 v4 (8) @ 2.1GHz
`$$b "-.__ Memory: 0.32 GiB / 1.92 GiB (16%)
`Y$$ Network: Unknown
`Y$$. BIOS: SeaBIOS 0.0 (04/01/2014)
`$$b.
`Y$$b.
`"Y$b._
`"""Note
Mirroring takes a lot of disk. Before you start, make sure you have enough space for the repository. You can find Debian repository sizes here.
Let’s Start
1. Preparation
There are many tools for syncing repositories. See the table for their features
| Tool | Key features | When to use |
|---|---|---|
apt-mirror | Simple and intuitive, good for quickly standing up a full mirror. | Individuals or small internal networks that want to get going fast without complex requirements. |
apt-mirror2 | A modern rewrite of apt-mirror in Python/asyncio with better performance and lower power draw, plus data integrity checks and GPG signature verification so the mirror never ends up corrupt. | Users who want better performance and reliability and to avoid corrupt mirrors mid-sync. |
debmirror | Powerful and flexible, with fine-grained syncing by architecture, distribution and component. Supports HTTP/HTTPS/FTP/rsync. | Situations that need precise control over what is synced, or networks that require a specific protocol. |
aptly | Enterprise-grade repository management. Beyond mirroring it supports snapshots, merging and publishing, suitable for version control and CI/CD integration. | Enterprise scenarios that need complex repository management and versioning. |
reprepro | A lightweight repository builder and manager that can build repos from a local pool of .deb files and also mirror, with flexible configuration. | Building a repository from local packages, or needing finer-grained mirror configuration. |
rsync | The most basic sync tool: mirror a whole directory directly, with efficient incremental transfers. | When you just need a simple, complete copy of the whole repository directory. |
Here we use ftpsync.
- Install
ftpsyncto mirror the repository, and a web server(nginx / apache)to serve it over HTTP
apt update
apt install ftpsync nginx -y- Create a dedicated user for syncing, as the official docs recommend for security
adduser mirror- Create the storage directory and set permissions for the mirror files
mkdir path/to/mirrir
chown mirror:mirror path/to/mirror2. Configuring the Sync
- Create the configuration file
mkdir /etc/ftpsync
vim /etc/ftpsync/ftpsync.confFill in the following
########################################################################
# ftpsync.conf - Debian 13 (trixie) amd64 + arm64 mirror configuration
########################################################################
# ---- Basics ----
# Mirror hostname, used to generate the trace file
MIRRORNAME=$(hostname -f)
# Destination directory for the mirror (must be empty or contain only pool data)
TO="/srv/mirrors/debian"
# Logs and mail (the log directory must exist beforehand)
LOGDIR="/home/mirror/log"
MAILTO="" # Leave empty to skip mail and read the logs instead
# Whether this is a leaf node (does not push to downstream mirrors)
HUB=false
# ---- Upstream ----
# Pick an upstream mirror that supports rsync
# Mirrors inside China such as Aliyun/Tencent are fine if they offer rsync
# This example uses the official ftp.debian.org; replace it with your upstream
RSYNC_HOST="ftp.debian.org"
RSYNC_PATH="debian"
# ---- Architecture filter ----
# Sync only amd64 and arm64, including source for later use
ARCH_INCLUDE="amd64 arm64 source"
# ---- Distribution filter ----
# Sync only trixie (Debian 13) and its components
# DIST_INCLUDE is the filter option supported by ftpsync
DIST_INCLUDE="trixie trixie-updates trixie-backports"
# ---- Log verbosity ----
# FULLLOGS must be enabled to see detailed rsync transfer progress
FULLLOGS=true
# ---- Deletion safety ----
# Guard against accidental deletion by capping the number of deletions
MAX_DELETE=1000
# ---- Extra rsync options ----
# --info=progress2 shows overall progress (use together with FULLLOGS)
RSYNC_EXTRA="--info=progress2"To sync several mirrors, create additional files in the same directory named
ftpsync-<mirrorName>.conf
- Run a sync to test the configuration
ftpsync sync:allIf it starts syncing without erroring out immediately, the config is valid. Press ^C and continue with the rest of the setup.
3. Configuring the Web Server
- Create the Nginx config file
vim /etc/nginx/site-avaliable/mirrorsFill in the following
server {
listen 80;
listen [::]:80;
server_name <your_domain>;
root path/to/mirror;
autoindex on;
autoindex_exact_size on;
autoindex_localtime on;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
charset utf-8;
keepalive_timeout 65;
access_log /var/log/nginx/mirrors.access.log;
error_log /var/log/nginx/mirrors.error.log;
location ~* /(dists|pool)/.*(InRelease|Release|Release.gpg|Packages|Sources)(\.gz|\.xz|\.bz2)?$ {
expires 1h;
add_header Cache-Control "public";
}
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
server_namethe server name; put your domain hererootthe site root; point it at the mirror directory If your URL containsdebian, add the followinglocation /debian/ { alias path/to/mirror; autoindex on; }
- Symlink the config into
sites-enabledto enable it
ln -s /etc/nginx/sites-available/mirror-debian /etc/nginx/sites-enabled/- Test the configuration
nginx -tIf the output has no errors, it’s fine. 4. Reload Nginx to apply
systemctl reload nginx- Fix permissions so
www-datacan read and traverse the mirror directory
chmod -R a+rX /mnt/Mirror/mirrors/debian4. Creating a Scheduled Job
- Install
crontabandflock
apt install cron flock- Create the lock directory, fix permissions and edit the crontab
mkdir /var/run/ftpsync
chown mirror:mirror /var/run/ftpsync
crontab -u mirror -eAdd the line 0 3 * * * flock -xn /var/run/ftpsync/ftpsync-debian.lock ftpsync sync:all
This syncs every day at 3am To sync every n hours instead, add
0 */n * * * flock -xn /var/run/ftpsync/ftpsync-debian.lock ftpsync sync:all
Save and exit
5. Running the First Sync
- Run
ftpsync sync:allfor the initial sync
Note: a full sync takes a long time; use something like
screento keep the session alive
- When the sync finishes, you can switch the client’s sources to the mirror
6. Testing
- Point a client at the mirror and test with
apt update && apt upgrade
root@test:~# apt update && apt upgrade
Get:1 http://mirrors.vm.homelab/debian-security trixie-security InRelease [43.4 kB]
Hit:2 http://mirrors.vm.homelab/debian trixie InRelease
Hit:3 http://mirrors.vm.homelab/debian trixie-updates InRelease
Fetched 43.4 kB in 0s (134 kB/s)
All packages are up to date.
Summary:
Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0If there are no errors and it’s fast, everything works. If you can, also add an internal DNS record so the mirror has its own internal domain name. That completes the mirror setup.
Conclusion
I’m not a professional, so please point out any mistakes! Thanks for reading.