Linux Tutorials

Create Local Ubuntu Mirror with apt-mirror

Running a local Ubuntu package mirror is the right move for large fleets, air-gapped environments, and lab networks where every VM would otherwise hammer archive.ubuntu.com on its own. The apt-mirror tool is the classic way to pull down a complete slice of a distribution and serve it to your own hosts from an internal HTTP server. Once you point your VMs at the mirror, apt update becomes nearly instant and you stop burning egress on the same metadata 200 times a day.

Original content from computingforgeeks.com - post 1433

This guide sets up an Ubuntu 24.04 LTS apt mirror, configures apt-mirror for the noble suites, runs the initial sync, and serves the result via Nginx. Clients inside the network then set their sources to point at the internal host.

Tested April 2026 on Ubuntu 24.04.4 LTS with apt-mirror from the noble main repository

Step 1: Provision storage and install apt-mirror

A complete mirror of Ubuntu 24.04 main, restricted, universe, and multiverse for a single architecture takes around 300-350 GB. Add another 150-200 GB for updates and security over the lifetime of the release. Size your storage accordingly and mount it at /var/spool/apt-mirror before you start:

df -hT /var/spool/apt-mirror

Confirm you have the headroom, then install the package:

sudo apt update
sudo apt install -y apt-mirror

The package creates an apt-mirror system user and the base directory layout for you. Verify with:

ls -la /var/spool/apt-mirror/

You should see three subdirectories owned by the new apt-mirror system user:

total 20
drwxr-xr-x 5 apt-mirror apt-mirror 4096 Apr 11 06:23 .
drwxr-xr-x 5 root       root       4096 Apr 11 06:23 ..
drwxr-xr-x 2 apt-mirror apt-mirror 4096 Oct  7  2022 mirror
drwxr-xr-x 2 apt-mirror apt-mirror 4096 Oct  7  2022 skel
drwxr-xr-x 2 apt-mirror apt-mirror 4096 Apr 11 06:23 var

Three subdirectories: mirror holds the actual package files after they’re downloaded, skel is the working metadata tree that apt-mirror builds before it moves things into place, and var holds download logs and the clean-up script.

Step 2: Configure which suites to mirror

The default config at /etc/apt/mirror.list ships pointing at an older Ubuntu release and needs to be rewritten for noble (24.04). Replace it with the file below:

sudo vi /etc/apt/mirror.list

Put this in:

############# config ##################
set base_path    /var/spool/apt-mirror
set nthreads     20
set _tilde 0

############# end config ##############

deb-amd64 http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse
deb-amd64 http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb-amd64 http://archive.ubuntu.com/ubuntu noble-backports main restricted universe multiverse
deb-amd64 http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse

clean http://archive.ubuntu.com/ubuntu
clean http://security.ubuntu.com/ubuntu

The deb-amd64 prefix restricts the mirror to the amd64 architecture. Drop it to deb if you need multi-arch packages (arm64, i386), but that roughly doubles the storage. The clean lines at the end tell the post-sync cleanup script which archives to prune obsolete files from.

Raise nthreads to 20 if you’re on a fast pipe. Too many parallel connections against a public mirror will sometimes get throttled, so start conservative if you see sync errors.

Step 3: Run the first sync

The initial sync is the big one. Launch it inside tmux so the download survives an SSH drop and keeps running in the background:

sudo -u apt-mirror apt-mirror

You’ll see a progress block like this once it starts working:

Downloading 68 index files using 20 threads...
Begin time: Sat Apr 11 06:24:01 2026
[20]... [19]... [18]... [17]... [16]...
End time: Sat Apr 11 06:24:47 2026

Proceed indexes: [PPPP]

76 GiB will be downloaded into archive.

On a 1 Gbps link the first sync takes around 15-30 minutes. On slower connections it’s often an overnight job. Subsequent runs only download deltas (new and updated packages) so they finish in minutes rather than hours.

Step 4: Schedule automatic syncs

Ubuntu’s apt archives refresh multiple times a day. Run a sync every few hours via a cron entry or systemd timer. The apt-mirror package ships an example cron job at /etc/cron.d/apt-mirror — just uncomment the line and adjust the schedule to something reasonable:

sudo vi /etc/cron.d/apt-mirror

Uncomment the default line and tighten the schedule so new packages land within a few hours of release:

# Run apt-mirror every 4 hours
0 */4 * * *   apt-mirror   /usr/bin/apt-mirror > /var/spool/apt-mirror/var/cron.log

The cron user is apt-mirror, not root. The system user was created when the package installed.

Step 5: Serve the mirror with Nginx

apt clients talk HTTP, so any web server that can serve a static directory tree will do. Nginx is the easiest option:

sudo apt install -y nginx

Create a minimal server block that serves the archive directory with autoindex enabled so clients can browse:

sudo vi /etc/nginx/sites-available/ubuntu-mirror

Paste in a minimal server block that exposes the local mirror directory tree:

server {
    listen 80;
    server_name mirror.lan.example;

    access_log /var/log/nginx/mirror.access.log;
    error_log  /var/log/nginx/mirror.error.log;

    location / {
        root  /var/spool/apt-mirror/mirror/archive.ubuntu.com/ubuntu;
        autoindex on;
    }
}

Enable the site, test the config, and reload:

sudo ln -sf /etc/nginx/sites-available/ubuntu-mirror /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

For production deployments, front this with SSL using Let’s Encrypt or your internal CA. See our Nginx installation guide on Ubuntu and Debian for full hardening and UFW rules to scope access to the LAN.

Step 6: Point client hosts at the mirror

On every Ubuntu 24.04 client that should use the mirror, edit /etc/apt/sources.list.d/ubuntu.sources (the new deb822 format introduced in 24.04) and change the URIs:

sudo vi /etc/apt/sources.list.d/ubuntu.sources

Replace the default URIs: line in each block with your internal mirror hostname:

Types: deb
URIs: http://mirror.lan.example
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Types: deb
URIs: http://mirror.lan.example
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Test the change by running apt update. The output should show every line pulling from mirror.lan.example instead of archive.ubuntu.com:

sudo apt update

If clients fail with a GPG verification error, it’s because the Signed-By path didn’t exist on the client. The standard ubuntu-keyring package provides it; reinstall it with sudo apt install --reinstall ubuntu-keyring.

Step 7: Disk usage reality check

Check how much space the mirror actually consumes so you can plan capacity for the full release lifecycle:

sudo du -sh /var/spool/apt-mirror/mirror

Run the apt-mirror cleanup shell script it generates at the end of each run to delete stale packages that are no longer referenced by the current index:

sudo -u apt-mirror bash /var/spool/apt-mirror/var/clean.sh

The first cleanup after a release point typically frees 10-20 GB of obsolete packages. Running it monthly is plenty.

Wrap up

A local apt mirror is the kind of infrastructure investment that pays itself back within a week in any environment with 10+ Ubuntu hosts. It also makes reproducible builds easier because you can pin downstream systems to a mirror snapshot taken at a known point in time. For a matching Rocky Linux 10 workflow see our Rocky 10 post-install guide, and for keeping the mirror running alongside automatic updates on clients, have a look at the Ubuntu 22.04 to 24.04 upgrade guide. For the systemd timer patterns that drive the sync, see our systemctl reference.

Related Articles

Debian Install Java 25 (JDK 25) on Debian 13 / Debian 12 Containers Install Dokku (Docker PaaS) on Ubuntu 24.04|22.04|20.04 Debian Install Apache Guacamole on Debian 13 / Debian 12 Databases Install PostgreSQL 11 on Debian 9 / Debian 8

2 thoughts on “Create Local Ubuntu Mirror with apt-mirror”

  1. Hi Josphat,

    Thanks for the how to.

    Quick edit for you. Line 4 is missing a ; on the end of the root directive, this will cause nginx to fail to start.

    Reply

Leave a Comment

Press ESC to close