Horrifically slow download speeds for download.proxmox.com?

Tmanok

Renowned Member
Hi Everyone, Hey PVE Staff,

Is it just me, or are the web servers for download.proxmox.com unbelievably slow? I'm updating a PBS instance after several months and realizing it will take hours.... This is naturally, inconveniently interfering with a maintenance window. Shame on me for not updating it sooner perhaps. But I am shocked by how slow this is. 20-40KB/s... :O

This is from a business fiber connection on Vancouver Island. I am used to seeing >10MB/s (80Mbps) on downloads even from popular servers, maybe 1MB/s from European servers. Dear PVE Developers / Staff: Please let me know if the no-subscription repository requires a North American mirror, I will volunteer one (i.e. for free) in 2027 outside of the Cascadia Earthquake zone in Kelowna.

Thanks!


Tmanok
 

Attachments

  • Screenshot 2026-08-21 at 19.12.35.png
    Screenshot 2026-08-21 at 19.12.35.png
    286.7 KB · Views: 16
Please check which specific IP of download.proxmox.com your PVE host resolves too, e.g. just using ping or dig or the like, as with that we can determine if your source IP gets correctly geoip-mapped and thus actually uses one of our north american CDN mirror hosts.
 
Had a similar experience when trying to download updates for our cluster in China.
I ended up singeling out the IP address for the Proxmox CDN in Singapore and added that IP address to the /etc/hosts file. (each host)
I basically pinned the single IP in Singapore to download.proxmox.com.
Yes, its going to be a problem if the CDN decides to host from a differen IP, but this was a good way for me to make sure we always use that repository.

Tip, I now use the proxmox-offline-mirror tool to grab all the updates of the internet (debian and proxmox) and accumulate them locally, I serve them back to the LAN using Nginx and all my Proxmox nodes use this internal repository to update themselves.
This way updates are only downloaded once, and it does not really matter how long it takes to download.
Updating the Proxmox nodes goes as fast as your LAN will go, since they use the internal repo.
This makes the maintenance window for updating a proxmox node as small as it could possibly be.
I loose more time enabling and disabling maintenance mode via CLI then I do actually updating :)
(Lets get that 'maintenance mode' button in the GUI, pretty please...)
 
A bit more on the proxmox-offline-mirror tool, it creates local repositories, with a snapshoted subdirectories.
These snapshots are folders that are named after the exact moment the sync was done, so each time you use the tool a new folder is created.
I found this very impractical sinds you want to point the sources.list on the proxmox hosts to the newest updates, but that path changes everytime.
(Or I did just not understand how to use the tool)
I created a script around the tool that will start the sync for each repositorie needed, create the snapshot, removes any snapshot older then 7 versions.
It then creates a symlink called 'latest' that points to the newest shapshot and does some garbage collecting to keep it clean.
The result is that you can point the sources.list on the proxmox hosts to that symlink, and it will therefore always point to the newest updates.
If allowed I can share the script here.
 
  • Like
Reactions: Johannes S
Hey H@rald,

Community members have shared scripts in the past, so unless a policy change has occurred, I'm sure you'd be welcome to link to a GitHub repository or something similar. I suspect that as China is moving away from the likes of Microsoft, Github may no longer be the safe harbour it once was, but surely an alternative exists that is reachable worldwide if not Github.

Thanks for describing this, I don't think your script was a waste of time because it adds a feature the developers never intended, you're using it for a rolling release, whereas Proxmox was offering this tool with the knowledge that a lot of enterprises would need it for systems that demand stability and to ensure that updates were uniform across all of their systems for troubleshooting and also for testing ahead of time. Having a static repository with a specific version means that even if the repository is updated, the hosts updating from it don't immediately switch over to a newer version unintentionally.

For your usecase, normally a Debian repository would be mirrored using ftpsync + rsync, which simply mirrors everything, and alternatively, perhaps even easier, would be something like apt-cacher which is a proxy that all the PVE and PBS hosts would point to, and every download would be cached for other nodes to use. The trouble is that this is on-demand rather than scheduled and so you get the latest files, and you use much less disk space, but you also have to wait the first time. Apt cacher also struggles are times with file corruption. I've never quite understood how or why, but I've tried several proxies over the years and never quite found them to be adequate. Mirrors are the most solid infrastructure you can have, and although they take up a lot more disk space and a lot more bandwidth, they can be useful after you reach more than 10-20 hosts all needing the same updates. I say 10-20 hosts because if you only update every few weeks and those updates only require 200MB to 500MB of internet bandwidth per host, 4GB to 10GB of bandwidth per month doesn't exactly justify the 1-3TB of disk you'll need to host a mirror, let alone the changes for the entire mirror. The good thing about PVE using its own repositories is that it's much smaller due to being concentrated on a single product, and every node is basically guaranteed to need 80% of the repository's updates. Personally, I'd much sooner host a PVE mirror than a Debian Mirror.

One more thing, I've used apt-mirror in the past, but it has failed hard on me twice, I would not recommend it. rsync is the way to go whenever possible.
Thanks,


Tmanok
 
  • Like
Reactions: h@rald and Onslow
Hey Tmanok,

I am not a coder by trade, so things like github are alien to me, I have a textfile, thats it.
The proxmox-offline-mirror tool might not be intended for how I use it, but it does the job pretty well.
Its not eating up much storage at all actually, the diskspace it uses at the moment is about 150Gb.
Besides them using hardlinks to setup the snapshots, which saves space, you can also pick-and-choose the downloaded packages.
When configuring the mirrors using the tool you can set filters for packages you know are not needed on a Proxmox node, like games and libreoffice.You can exclude complete categories, without having to go in much detail.

With regards to having controll over the what snapshot each proxmox node uses when updating, the controll is still there in this case.
Even though I have the nodes pointing towards the symlink to 'latest', they only update when you manually trigger the button.
If I need them to stay on a certain level I can always change them to point to any off the last 7 snapshots available on the offline-mirror.

For me the script was a need-to-have, as otherwise I could just not get consistent results when updating my nodes inside mainland China.
Since the offline-mirror also reduces the window where a node is in maintenance mode, I found that its also a nice-to-have for my cluster in the EU.

I will try and add the script and the proxmox-offline-mirror config file to this post using the code snippet.
Anyone using the script be advised, this was created to scratch my particular itch, it might not fit your needs.
Also know that I am not a code, I started out with simple tests, created a bash script, but in the end I had Copilot validate the script.
Copilot added some validations to the script and simplified it. (so AI was involved)

The configuration file used for the proxmox-offline-mirror tool (it points to a dutch repo, might wanna change that)
Note the skipped packages and package sections.
The config for our production cluster has the enterprise repository in it, the one below points to the no-subscription repo.

Code:
mirror: debian_trixie_security
       architectures amd64
       architectures all
       base-dir /var/lib/proxmox-offline-mirror/mirrors/
       ignore-errors false
       key-path /usr/share/keyrings/debian-archive-keyring.gpg
       repository deb http://security.debian.org/debian-security trixie-security main contrib non-free-firmware
       skip-packages "linux-source-*"
       skip-packages "*-doc"
       skip-packages "thunderbird*"
       skip-packages "wine*"
       skip-packages "libreoffice*"
       skip-sections debug
       skip-sections games
       skip-sections fonts
       skip-sections gnome
       skip-sections documentation
       skip-sections education
       skip-sections electronics
       sync true
       verify true
 
mirror: debian_trixie_main
       architectures amd64
       architectures all
       base-dir /var/lib/proxmox-offline-mirror/mirrors/
       ignore-errors false
       key-path /usr/share/keyrings/debian-archive-keyring.gpg
       repository deb http://debian.snt.utwente.nl/debian/ trixie main contrib non-free-firmware
       skip-packages "linux-source-*"
       skip-packages "*-doc"
       skip-packages "thunderbird*"
       skip-packages "wine*"
       skip-packages "libreoffice*"
       skip-sections debug
       skip-sections games
       skip-sections fonts
       skip-sections gnome
       skip-sections documentation
       skip-sections education
       skip-sections electronics
       sync true
       verify true
 
mirror: debian_trixie_updates
       architectures amd64
       architectures all
       base-dir /var/lib/proxmox-offline-mirror/mirrors/
       ignore-errors false
       key-path /usr/share/keyrings/debian-archive-keyring.gpg
       repository deb http://debian.snt.utwente.nl/debian/ trixie-updates main contrib non-free-firmware
       skip-packages "linux-source-*"
       skip-packages "*-doc"
       skip-packages "thunderbird*"
       skip-packages "wine*"
       skip-packages "libreoffice*"
       skip-sections debug
       skip-sections games
       skip-sections fonts
       skip-sections gnome
       skip-sections documentation
       skip-sections education
       skip-sections electronics
       sync true
       verify true
 
mirror: proxmox-ve-no-subscription
       architectures amd64
       architectures all
       base-dir /var/lib/proxmox-offline-mirror/mirrors
       ignore-errors false
       key-path /usr/share/keyrings/proxmox-archive-keyring.gpg
       repository deb http://download.proxmox.com/debian/pve trixie pve-no-subscription
       sync true
       verify true

Below is the script itself :

Code:
#!/usr/bin/env bash
set -euo pipefail
log() {
   echo "$(date '+%F %T') $*"
}
# Mirror locations
declare -A REPOS=(
   [debian_trixie_main]="/var/lib/proxmox-offline-mirror/mirrors/debian_trixie_main"
   [debian_trixie_security]="/var/lib/proxmox-offline-mirror/mirrors/debian_trixie_security"
   [debian_trixie_updates]="/var/lib/proxmox-offline-mirror/mirrors/debian_trixie_updates"
   [proxmox-ve-no-subscription]="/var/lib/proxmox-offline-mirror/mirrors/proxmox-ve-no-subscription"
)
# Number of snapshots to retain
KEEP_SNAPSHOTS=7
# Include hidden entries and avoid literal glob expansion when no matches exist
shopt -s dotglob nullglob
cleanup_snapshots() {
   local topdir="$1"
   local mirror="$2"
   local snapshots=()
   local newest
   # Collect snapshot directories sorted by modification time (newest first)
   mapfile -t snapshots < <(
       find "$topdir" \
           -mindepth 1 \
           -maxdepth 1 \
           -type d \
           ! -name latest \
           -printf '%T@ %p\n' |
       sort -rn |
       cut -d' ' -f2-
   )
   if (( ${#snapshots[@]} == 0 )); then
       log "WARNING: No snapshots found for $mirror"
       return 1
   fi
   newest="${snapshots[0]}"
   log "Keeping latest snapshot: $(basename "$newest")"
   # Update the "latest" symlink atomically
   ln -sfn "$newest" "$topdir/latest"
   # Remove snapshots older than the retention count
   if (( ${#snapshots[@]} > KEEP_SNAPSHOTS )); then
       for oldsnap in "${snapshots[@]:$KEEP_SNAPSHOTS}"; do
           log "Removing old snapshot: $(basename "$oldsnap")"
           proxmox-offline-mirror mirror snapshot remove \
               "$mirror" \
               "$(basename "$oldsnap")"
       done
   fi
}
for mirror in "${!REPOS[@]}"; do
   log "Creating snapshot for $mirror"
   proxmox-offline-mirror mirror snapshot create "$mirror"
   cleanup_snapshots "${REPOS[$mirror]}" "$mirror"
done
log "Running garbage collection"
proxmox-offline-mirror mirror gc
log "Completed successfully"

Feel free to use the script / alter it to your needs, but I can not offer much help if the script does not work for you.

The script was added to our internal repository, as a service, and this service is started (by a timer, not cron) each night at 01:00.
Nginx is running on the internal repo to publish the repositories to the Proxmox nodes.

As an example here is the proxmox.sources apt file :
Code:
Types: deb
URIs: http://<FQDN-of-internal-repo-host>/proxmox-ve-no-subscription/latest
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
 
Last edited: