Building a Debian 13 Mirror with ftpsync and Nginx

Published 2026-09-20 12:45 1221 words 7 min read

Madobi Nanami avatar

Madobi Nanami

Nanami 的个人技术博客,记录网络、运维、虚拟化与逆向的实践。

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...

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

ToolKey featuresWhen to use
apt-mirrorSimple 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-mirror2A 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.
debmirrorPowerful 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.
aptlyEnterprise-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.
repreproA 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.
rsyncThe 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.

  1. Install ftpsync to mirror the repository, and a web server (nginx / apache) to serve it over HTTP
apt update
apt install ftpsync nginx -y
  1. Create a dedicated user for syncing, as the official docs recommend for security
adduser mirror
  1. Create the storage directory and set permissions for the mirror files
mkdir path/to/mirrir
chown mirror:mirror path/to/mirror

2. Configuring the Sync

  1. Create the configuration file
mkdir /etc/ftpsync
vim /etc/ftpsync/ftpsync.conf

Fill 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

  1. Run a sync to test the configuration
ftpsync sync:all

If 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

  1. Create the Nginx config file
vim /etc/nginx/site-avaliable/mirrors

Fill 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_name the server name; put your domain here root the site root; point it at the mirror directory If your URL contains debian, add the following

  location /debian/ {
      alias path/to/mirror;
      autoindex on;
  }
  1. Symlink the config into sites-enabled to enable it
ln -s /etc/nginx/sites-available/mirror-debian /etc/nginx/sites-enabled/
  1. Test the configuration
nginx -t

If the output has no errors, it’s fine. 4. Reload Nginx to apply

systemctl reload nginx
  1. Fix permissions so www-data can read and traverse the mirror directory
chmod -R a+rX /mnt/Mirror/mirrors/debian

4. Creating a Scheduled Job

  1. Install crontab and flock
apt install cron flock
  1. Create the lock directory, fix permissions and edit the crontab
mkdir /var/run/ftpsync
chown mirror:mirror /var/run/ftpsync
crontab -u mirror -e

Add 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

  1. Run ftpsync sync:all for the initial sync

Note: a full sync takes a long time; use something like screen to keep the session alive

  1. When the sync finishes, you can switch the client’s sources to the mirror

6. Testing

  1. 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: 0

If 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.


Random Posts

Loading...