> For the complete documentation index, see [llms.txt](https://en.itmatic101.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://en.itmatic101.com/virtualisation/proxmox-offline-mirror-pom-for-offline-updates.md).

# Proxmox Offline Mirror (POM) for offline updates

Keeping Proxmox VE systems up to date is straightforward when Internet access is available. However, in air-gapped or highly restricted environments, administrators need an alternative method to distribute updates securely and consistently. This article demonstrates how to build an offline update infrastructure using Proxmox Offline Mirror (POM). The solution allows a dedicated mirror server with Internet access to download and maintain Proxmox and Debian repositories, while isolated Proxmox nodes consume updates from a locally hosted mirror.

The environment consists of the following components:

* Mirror Server - downloads and synchronizes Proxmox and Debian repositories from the Internet
* Storage - dedicated 400 GB disk used to store repository snapshots
* NGINX web server - publishes mirrored repositories to offline systems
* Proxmox VE nodes - air-gapped systems that consume updates through HTTP

The overall workflow is:

1. Download repository content on the mirror server.
2. Create repository snapshots using Proxmox Offline Mirror.
3. Synchronize snapshots to an update medium.
4. Host the mirrored APT repositories on NGINX web server via HTTP.
5. Configure the web address URL as APT sources on offline Proxmox nodes.
6. Perform normal package upgrades without Internet access.

## Preparing Proxmox Offline Mirror (POM) Server

Debian 13 LXC container has been used as the base OS to setup Proxmox Offline Mirror (POM) server in this lab setup. Proxmox official documentation can be found here - <https://pom.proxmox.com/installation.html> and following are the steps to install it. Since it's Debian 13 LXC container in Proxmox VE, I have used the default root account to setup the POM server.

```bash
# Install prerequisites
apt update
apt install -y wget ca-certificates

# Download the current Trixie Proxmox keyring
wget https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg -O /usr/share/keyrings/proxmox-archive-keyring.gpg

# Verify the keyring
sha256sum /usr/share/keyrings/proxmox-archive-keyring.gpg
136673be77aba35dcce385b28737689ad64fd785a797e57897589aed08db6e45 /usr/share/keyrings/proxmox-archive-keyring.gpg

# Debian 13 uses the newer .sources Deb822 format
nano /etc/apt/sources.list.d/pbs-client.sources
Types: deb
URIs: http://download.proxmox.com/debian/pbs-client
Suites: trixie
Components: main
Signed-by: /usr/share/keyrings/proxmox-archive-keyring.gpg

# update again and install proxmox-offline-mirror package
apt update
apt install proxmox-offline-mirror -y

# verify the installation
which proxmox-offline-mirror
```

### Configuring repository mirrors

Here is how to use the guided setup utility comes with proxmox-offline-mirror installation.

```bash
proxmox-offline-mirror setup                                                     

Initializing new config.                                                         

Select Action:                                                                   
   0.) Add new mirror entry                                                      
   1.) Add new subscription key                                                  
   2.) Quit                                                                      
Choice ([0]):                                                                    

Guided Setup ([yes]):                                                            

Select distro to mirror                                                          
   0.) Proxmox VE                                       
   1.) Proxmox Backup Server
   2.) Proxmox Mail Gateway
   3.) Proxmox Ceph
   4.) Debian
Choice: 0

Select release
   0.) Trixie
   1.) Bookworm
   2.) Bullseye
Choice ([0]): 0

Select repository variant
   0.) Enterprise repository
   1.) No-Subscription repository
   2.) Test repository
Choice ([0]): 1

Should missing Debian mirrors for the selected product be auto-added ([yes]): 

Configure filters for Debian mirror trixie / main:
        Enter list of package sections to be skipped ('-' for None) ([debug,games]): 
        Enter list of package names/name globs to be skipped ('-' for None): -

Configure filters for Debian mirror trixie / updates:
        Enter list of package sections to be skipped ('-' for None) ([debug,games]): 
        Enter list of package names/name globs to be skipped ('-' for None): -

Configure filters for Debian mirror trixie / security:
        Enter list of package sections to be skipped ('-' for None) ([debug,games]): 
        Enter list of package names/name globs to be skipped ('-' for None): -

Enter mirror ID ([pve_trixie_no-subscription]): 

Enter (absolute) base path where mirrored repositories will be stored ([/var/lib/proxmox-offline-mirror/mirrors/]): 

Should already mirrored files be re-verified when updating the mirror? (io-intensive!) ([yes]): 

Should newly written files be written using FSYNC to ensure crash-consistency? (io-intensive!) ([yes]): 

Config entry 'debian_trixie_main' added
Run "proxmox-offline-mirror mirror snapshot create --config '/etc/proxmox-offline-mirror.cfg' 'debian_trixie_main'" to create a new mirror snapshot.

Config entry 'debian_trixie_updates' added
Run "proxmox-offline-mirror mirror snapshot create --config '/etc/proxmox-offline-mirror.cfg' 'debian_trixie_updates'" to create a new mirror snapshot.

Config entry 'debian_trixie_security' added
Run "proxmox-offline-mirror mirror snapshot create --config '/etc/proxmox-offline-mirror.cfg' 'debian_trixie_security'" to create a new mirror snapshot.

Config entry 'pve_trixie_no-subscription' added
Run "proxmox-offline-mirror mirror snapshot create --config '/etc/proxmox-offline-mirror.cfg' 'pve_trixie_no-subscription'" to create a new mirror snapshot.

Existing config entries:
mirror 'pve_trixie_no-subscription'
mirror 'debian_trixie_updates'
mirror 'debian_trixie_main'
mirror 'debian_trixie_security'

Select Action:
   0.) Add new mirror entry
   1.) Add new medium entry
   2.) Add new subscription key
   3.) Quit
Choice ([0]): 3
```

Now it's ready to start pulling the packages from the sources to its local mirror repositories respectively.

```bash
proxmox-offline-mirror mirror snapshot create-all 
```

It will take a while to complete the download for all of those four repositories setup in the previous step. The directories structure should be displayed as below. **Don't change the directory structure.** Let POM manage it and have Nginx expose the parent directory.

```bash
find /var/lib/proxmox-offline-mirror/mirrors -maxdepth 3 -type d | sort

/var/lib/proxmox-offline-mirror/mirrors/
├── pve_trixie_no-subscription/
│   └── <snapshot-id>/
│       ├── dists/
│       └── pool/
│
├── debian_trixie_main/
│   └── <snapshot-id>/
│       ├── dists/
│       └── pool/
│
├── debian_trixie_updates/
│   └── <snapshot-id>/
│       ├── dists/
│       └── pool/
│
└── debian_trixie_security/
    └── <snapshot-id>/
        ├── dists/
        └── pool/
```

### Configuring Nginx to host apt packages internally

Here is how to install and setup Nginx webserver for POM server.

```bash

# update and install Nginx and CURL
apt update
apt install nginx curl -y

# verify the installatiom and systemd status
systemctl status nginx
* nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-08-24 13:15:15 UTC; 1s ago
 Invocation: bfcf4b0ecbe34ce18fd4076dc1635bf2
       Docs: man:nginx(8)
    Process: 2091 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 2092 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 2120 (nginx)
      Tasks: 3 (limit: 34852)
     Memory: 3.7M (peak: 7.8M)
        CPU: 38ms
     CGroup: /system.slice/nginx.service
             |-2120 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             |-2123 "nginx: worker process"
             `-2124 "nginx: worker process"

Aug 24 13:15:15 pom systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Aug 24 13:15:15 pom systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.

# check with CURL
curl http://localhost/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

After the installation, we need to configure Nginx in accordance with the directories structure configured by POM guided utility. Here is how to configure the webserver.

```bash
# configure Nginx site for POM
nano /etc/nginx/sites-available/pom
server {
    listen 80;
    listen [::]:80;

    server_name pom.local;

    root /var/lib/proxmox-offline-mirror/mirrors;

    autoindex on;

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

    location / {
        try_files $uri $uri/ =404;
    }
}

# set POM directory to the appropriate permission
chmod 755 /var/lib/proxmox-offline-mirror
chmod 755 /var/lib/proxmox-offline-mirror/mirrors 

# enable the Nginx config
rm -f /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/pom /etc/nginx/sites-enabled/pom

# test the config syntax
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# reload the Nginx daemon to apply the changes made
systemctl reload nginx

# verify if the webserver hosting the POM repos properly
curl http://localhost
<html>
<head><title>Index of /</title></head>
<body>
<h1>Index of /</h1><hr><pre><a href="../">../</a>
<a href="debian_trixie_main/">debian_trixie_main/</a>                                24-Aug-2026 13:07                   -
<a href="debian_trixie_security/">debian_trixie_security/</a>                            24-Aug-2026 12:55                   -
<a href="debian_trixie_updates/">debian_trixie_updates/</a>                             24-Aug-2026 12:55                   -
<a href="pve_trixie_no-subscription/">pve_trixie_no-subscription/</a>                        24-Aug-2026 12:55                   -
</pre><hr></body>
</html>
```

### Test the actual apt repository

This is more important than simply seeing the directory.

Find the snapshot:

```bash
find /var/lib/proxmox-offline-mirror/mirrors -name Release -o -name InRelease
```

For example, you might find:

```
/var/lib/proxmox-offline-mirror/mirrors/pve_trixie_no-subscription/<snapshot>/dists/trixie/Release
```

Then test one of the URLs:

```bash
curl -I http://localhost/pve_trixie_no-subscription/<snapshot>/dists/trixie/Release
```

You should get 200 OK if all properly configured.

```
HTTP/1.1 200 OK
```

## Configure Proxmox VE 9 node(s)

Your PVE repository is:

```
pve_trixie_no-subscription
```

So the PVE node should ultimately use something like:

```
deb [check-valid-until=false] http://pom.local/pve_trixie_no-subscription/<snapshot> trixie pve-no-subscription
```

For example:

```
deb [check-valid-until=false] http://pom.local/pve_trixie_no-subscription/2026-08-24T12:00:00Z trixie pve-no-subscription
```

The exact `<snapshot>` value should come from your POM-generated directory structure rather than being guessed.

Your POM configuration automatically created three Debian repositories:

```
debian_trixie_main
debian_trixie_updates
debian_trixie_security
```

This is useful because your PVE hosts will need Debian packages as well as the Proxmox packages. Conceptually, your PVE node(s) will consume:

```
                    Nginx
                      │
       ┌──────────────┼──────────────┐
       │              │              │
       ▼              ▼              ▼
 Debian main       Debian updates   Debian security
       │              │              │
       └──────────────┼──────────────┘
                      │
                      ▼
              Proxmox VE packages
```

Your resulting setup should be like this:

```
                         Internet
                            │
                            ▼
              ┌──────────────────────────┐
              │ Debian 13 POM Server     │
              │                          │
              │ proxmox-offline-mirror   │
              │                          │
              │ /var/lib/.../mirrors/    │
              │          │               │
              │          ▼               │
              │        Nginx             │
              │        TCP/80            │
              └──────────┬───────────────┘
                         │
              ┌──────────┼──────────┐
              │          │          │
              ▼          ▼          ▼
             PVE1       PVE2       PVE3
```

## Conclusion

The Proxmox Offline Mirror (POM) setup provides a centralized and controlled package distribution mechanism for the Proxmox VE environment. In this implementation, POM is deployed on a Debian 13 system and configured to mirror the Proxmox VE 9 Trixie **No-Subscription** repository together with the required Debian Trixie repositories: `main`, `updates`, and `security`.

The mirrored repositories are stored under:

```
/var/lib/proxmox-offline-mirror/mirrors/
```

and are exposed internally through an Nginx web server. Nginx acts as a simple HTTP repository server, allowing Proxmox VE nodes to retrieve packages from the local mirror without requiring direct access to the upstream Proxmox and Debian repositories.

The resulting architecture separates **repository synchronization**, **repository storage**, and **repository distribution**:

```
                    Internet
                       │
                       ▼
             ┌───────────────────┐
             │ Debian 13 POM Host│
             │                   │
             │ Proxmox Offline   │
             │ Mirror            │
             └─────────┬─────────┘
                       │
                Local repository
                   snapshots
                       │
                       ▼
                 ┌──────────┐
                 │  Nginx   │
                 │ HTTP :80 │
                 └────┬─────┘
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
        PVE-01      PVE-02      PVE-03
```

The configured POM repositories are:

```
pve_trixie_no-subscription
debian_trixie_main
debian_trixie_updates
debian_trixie_security
```

Using POM snapshots also provides an important operational advantage: Proxmox nodes can consume a **known, immutable repository snapshot** instead of depending on whatever package versions happen to be available upstream at a particular time. New snapshots can be synchronized and tested independently before being adopted by the production or homelab cluster.

Nginx does not modify or manage the repository contents; it simply publishes the POM-managed directory tree over HTTP. This keeps responsibilities clean: **POM manages repository synchronization and snapshots, while Nginx provides repository access to the Proxmox nodes**.

Overall, this architecture provides a lightweight and maintainable solution for environments where Proxmox VE nodes have limited or no Internet connectivity. It also establishes a good foundation for future automation, including scheduled POM synchronization, snapshot retention, internal DNS, HTTPS, repository validation, and automated rollout of approved snapshots across the Proxmox cluster.

The key benefit is that the Proxmox infrastructure can remain operationally isolated from the Internet while still receiving controlled Debian and Proxmox updates through a single, centrally managed internal repository.
