In this post I will describe the necessary steps to host your own APT mirror that you can use in your local network to speed up package installations and updates, and save bandwidth.

Requirements

  • A Debian server/VM
  • About ~750GB of HDD space (more if you want to host multiple architectures and/or more repositories)

Installation

Installing the system itself is outside the scope of this post, you can refer to the post linked above for a guide on installing Debian remotely, you can use a different method if you prefer, as long as you have a functioning system in the end.

Setting up apt-mirror

After you installed Debian and did the initial hardening/configuration according to your needs, you can start with installing the dependencies.

  1. Install apt-mirror: apt install apt-mirror apt-transport-https ca-certificates
  2. Configure apt-mirror:
    1. Open the configuration file /etc/apt/mirror.list using your favorite editor and paste the following:
      ############# config ##################
              
       ## This option sets the base path for apt-mirror to use.
       set base_path    /var/apt-mirror
              
       ## The options below allow you to change the locations
       ## that are usually located in the base_path.
       # set mirror_path  $base_path/mirror
       # set skel_path    $base_path/skel
       # set var_path     $base_path/var
       # set cleanscript $var_path/clean.sh
              
       ## You can change the default architecture using this option.
       # set defaultarch  <running host architecture>
              
       ## You can use the options below to run a script
       ## after the mirroring finished.
       # set postmirror_script $var_path/postmirror.sh
       # set run_postmirror 0
              
       ## The number of threads to use for downloading packages. 
       set nthreads 8
              
       ## Packages with a tilde (~) in their version are pre-release,
       ## we don't want them in the mirror.
       set _tilde 0
              
       ############# end config ##############
              
       ##
       # Debian v12 Bookworm
       ##
              
       # Debian Bookworm
       deb https://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
       deb-src https://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
       deb-amd64 https://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
       # deb-arm64 https://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
              
       # Debian Bookworm Updates
       deb https://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
       deb-src https://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
       deb-amd64 https://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
       # deb-arm64 https://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
              
       # Debian Bookworm Backports
       deb https://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware
       deb-src https://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware
       deb-amd64 https://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware
       # deb-arm64 https://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware
              
       # Debian Bookworm Security
       deb https://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
       deb-src https://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
       deb-amd64 https://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
       # deb-arm64 https://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
              
       ##
       # Other Stuff
       ##
              
       # Clean Scripts
       clean https://deb.debian.org/debian-security
      
    2. Make sure to update the configuration according to your needs, especially the base_path and nthreads options, in case you want/need to use a different storage path or more threads to sync the mirror.
    3. In case you want to also mirror arm64 packages, you can uncomment the deb-arm64 statements in the repo list.
  3. Add a CRON Job for the mirroring:
    1. Use crontab -e to open the crontab editor using your favorite editor
    2. Add: 0 1 * * * /usr/bin/apt-mirror > /var/log/apt-mirror.log
    3. This will run every day at 1:00, you can of course change this as you want.
  4. Run the first mirroring (manually)
    1. I’d recommend using screen for this (or any other similar tool you prefer): screen -S first-mirror
    2. Run apt-mirror and wait until the execution finishes

Regarding HTTPS

There are ongoing discussions on various websites about whether APT should use HTTPS/TLS by default (currently, it does not). I used HTTPS URLs for the mirror in this guide to provide a secure default, but the webserver of the mirror itself (which is assumed to run in the local network) is running with HTTP in order to keep the guide simple. You can find some examples of configuring NGINX for TLS on this blog (for example you could search for 443 to find config examples).

Setting up NGINX

Now that the mirror content is synced, you need a way to fetch the mirrored content from your server. NGINX will be used in this guide together with the fancyindex module in order to render nice looking indexes.

  1. Install NGINX: apt install nginx libnginx-mod-http-fancyindex
  2. Configure NGINX
    1. Open the default config at /etc/nginx/sites-enabled/default using your favorite editor
    2. Paste the following:
      server {
       listen 80 default_server;
       server_name _;
      
       access_log /var/log/nginx/mirror.access.log;
       error_log  /var/log/nginx/mirror.error.log;
      
       server_name_in_redirect off;
      
       autoindex off;
       server_tokens off;
      
       root /var/www/html;
      
       location /debian {
           alias /var/apt-mirror/mirror/deb.debian.org/debian;
      
           fancyindex on;
           fancyindex_exact_size off;
           fancyindex_header /head.html;
           fancyindex_footer /foot.html;
       }
      
       location /debian-security {
           alias /var/apt-mirror/mirror/deb.debian.org/debian-security;
      
           fancyindex on;
           fancyindex_exact_size off;
           fancyindex_header /head.html;
           fancyindex_footer /foot.html;
       }
      
       error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;
      
       location /error.html {
           root /var/www/html;
           internal;
       }
      }
      
    3. Don’t forget to update the paths of the alias params in case you changed the path in the apt-mirror config
  3. Add the HTML files (I’ll provide unstyled examples):
    1. Add /var/www/html/index.html (replace MIRROR_DOMAIN with the actual DNS name (or IP(?)) of the mirror:
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <title>APT Mirror</title>
        </head>
        <body>
        <h1>APT Mirror</h1>
        <p>This is an APT mirror.</p>
        <h2>Currently hosting:</h2>
        <ul>
            <li>
                <a href="/debian">/debian</a>
                <ul>
                    <li>bookworm</li>
                    <li>bookworm-updates</li>
                    <li>bookworm-backports</li>
                </ul>
            </li>
            <li>
                <a href="/debian-security">/debian-security</a>
                <ul>
                    <li>bookworm-security</li>
                </ul>
            </li>
        </ul>
        <h2>Usage</h2>
        <p>Select the repos you need and add them to your <code>/etc/apt/sources.list</code>:</p>
        <details>
            <summary>Debian v12 (bookworm)</summary>
            <pre>
        # Debian Bookworm
        deb http://MIRROR_DOMAIN/debian bookworm main contrib non-free non-free-firmware
        deb-src http://MIRROR_DOMAIN/debian bookworm main contrib non-free non-free-firmware
              
        # Debian Bookworm Updates
        deb http://MIRROR_DOMAIN/debian bookworm-updates main contrib non-free non-free-firmware
        deb-src http://MIRROR_DOMAIN/debian bookworm-updates main contrib non-free non-free-firmware
              
        # Debian Bookworm Security
        deb http://MIRROR_DOMAIN/debian-security bookworm-security main contrib non-free non-free-firmware
        deb-src http://MIRROR_DOMAIN/debian-security bookworm-security main contrib non-free non-free-firmware
              
        # Debian Bookworm Backports
        deb http://MIRROR_DOMAIN/debian bookworm-backports main
        deb-src http://MIRROR_DOMAIN/debian bookworm-backports main
                </pre>
        </details>
        </body>
        </html>
      
    2. Add /var/www/html/error.html:
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <title>APT Mirror</title>
        </head>
        <body>
        <h1>Error!</h1>
        <p class="lead">An error occurred.</p>
        <p><a href="/">Main Page</a>
        </body>
        </html>
      
    3. Add /var/www/html/head.html:
        <html lang="en">
        <head>
            <title>APT Mirror</title>
        </head>
        <body>
        <h1>APT Mirror</h1>
        <p>Welcome to the APT Mirror.</p>
        <h1>
      
      • This gets cut off at the open <h1> because the fancyindex module will render the title of the folder it’s showing after that and close it.
    4. Add /var/www/html/foot.html:
        <hr />
        <p>Thanks for using the APT Mirror.</p>
        </body>
        </html>
      
    5. Remove /var/www/html/index.nginx-debian.html in case it exists
    6. Ensure the files are not world-writable and belong to either root or www-data
  4. Confirm that the NGINX config is valid by running: nginx -t
  5. Restart NGINX: systemctl restart nginx

You should now be able to open http://MIRROR_DOMAIN/ (changed to your actual DNS name) in a browser and also view the repo contents. I added example usage instructions into the example index.html, so you can copy the example sources.list contents from there.

The next step would be adding styles to the mirror pages so it looks better, and of course add more/different repos/architectures in case you need them.

I hope this post was useful. If you have any suggestions or found errors please let me know!