Backup download cannot resume after interruption because email token has already been consumed

There was a previous backup-download issue which appears to have been fixed in 2026.01.0-latest. I believe this is a separate current issue involving recovery from an interrupted backup download, rather than the old initial-download failure.

One possibility raised in that earlier discussion was that the one-time backup link could prevent a browser from resuming an interrupted download: once the first request has consumed the link, a subsequent resume request may no longer be authorized.

I have now been able to reproduce exactly that behaviour on current Discourse.

Reproduction

I was downloading a local Discourse backup of:

4,231,639,143 bytes

using Safari on iOS.

The download was progressing normally. I have a screen recording showing it increasing from approximately:

https://youtube.com/shorts/_Pjc-kxJ1OE?feature=shared

313.3 MB to 317.8 MB

I then switched away from Safari for only around 15 seconds.

When I returned to Safari, the download had stopped at approximately:

319.8 MB

and Safari displayed its retry control.


The Caddy access log for the original request shows:

GET /admin/backups/...

HTTP/3

Range: none

status: 200

Content-Length: 4231639143

size: 319864024

I then used Safari’s own retry control.

Safari made a genuine HTTP byte-range request:

GET /admin/backups/...

HTTP/3

Range: bytes=319799904-

status: 422

So Safari was not simply attempting another full download. It was correctly trying to resume the existing file at approximately the point where the previous transfer stopped.

Discourse rejected that Range request with 422.

Current Discourse code

Looking at current Admin::BackupsController#show, the sequence is effectively:

EmailBackupToken.compare(current_user.id, token)

        ↓

find backup

        ↓

EmailBackupToken.del(current_user.id)

        ↓

send_file

So the initial authenticated request consumes the email token before the multi-gigabyte transfer has actually completed.

If that transfer is subsequently interrupted, Safari makes another request such as:

Range: bytes=319799904-

but that request goes through /admin/backups/… again and the email token has already been deleted.

That appears to explain the 422 I captured.

The authenticated session is still required as well as the backup token, so I am not suggesting simply making the existing token indefinitely reusable.

Rather, I am wondering whether there should be some bounded way for the same authenticated administrator to resume the same already-authorized backup download.

For example, that might be a short-lived download authorization associated with the same user and backup, although maintainers may have a better way of handling this.

Why this is becoming easier to reproduce on iOS

The interruption which exposed this also appears similar to reports from users of recent iOS versions.

There are at least three reports hosted on Apple Community:

older reports

“Safari Background Downloads Failing After IOS 26 update” — October 2025

Safari Background Downloads Failing After… - Apple Community

The user reports larger Safari downloads failing when running in the background after upgrading to iOS 26. They also upgraded an iPhone 15 Pro to iOS 26 and reported the same behaviour.

“Safari iOS Background Download Issue - Cancels on Minimize” — December 2025

Safari iOS Background Download Issue - Ca… - Apple Community

Reported on an iPhone 15 running iOS 26.1. Downloads start normally but cancel after switching to another app or minimizing Safari.

“Safari can’t complete a large download” — April 2026

Reported on an iPhone 17 Pro Max running iOS 26.4. Large downloads reportedly pause shortly after locking the device, leaving Safari, or changing between cellular and Wi-Fi. The author specifically reports good signal and says the same downloads complete on PC, Mac and Android.

I don’t think those community reports are enough to say that Apple has confirmed an iOS regression.

However, they are consistent with my screen recording: the backup was downloading normally, I switched away from Safari briefly, and when I returned the transfer had become interrupted.

The important Discourse-side point is independent of why the interruption occurred:

large backup transfer is interrupted

        ↓

browser attempts standards-based Range resume

        ↓

backup token has already been consumed

        ↓

resume receives 422

Server-side observations

This does not appear to be a general problem with the current nginx/sendfile path.

The same multi-GB backup path has successfully completed:

  • from Firefox/Linux over HTTP/3; and
  • from the same iPhone over Wi-Fi using HTTP/3.

Current nginx also advertises byte-range support.

So I’m not suggesting that HTTP/3, Caddy or nginx is intrinsically unable to deliver the backup.

In this particular reproduction, the original transfer was interrupted after about 320 MB, Safari subsequently made a valid-looking Range request, and that second request was rejected by Discourse.

Would it make sense for an already-authorized backup download to have some short-lived mechanism which allows the same authenticated administrator to resume that same file after an interrupted transfer?

I had a look through the history and current test coverage to get a better idea of what a possible fix would need to preserve.

The one-use behaviour dates back to the March 2017 security-hardening change:

That commit introduced emailed backup-download tokens and also consumed the token immediately once the download request had been authorized.

The same ordering still exists in current Admin::BackupsController#show:

The underlying token implementation is also still very simple:

EmailBackupToken stores one Redis token per user with a one-day expiry. It is user-scoped rather than being tied to a particular backup.

Looking through that file’s history, the changes since 2017 appear to have been mechanical/style changes rather than changes to the authorization model.

That makes simply leaving the existing emailed token reusable seem undesirable: it would weaken the original one-use restriction, and the token itself is not scoped to the particular backup in the URL.

The current request tests are here:

They cover an initial valid download, invalid tokens, invalid/missing backup IDs and permissions, but I couldn’t find coverage for an interrupted transfer followed by a Range: request.

The API request spec likewise documents the ordinary filename + token download path:

A possible patch direction therefore seems to be to preserve the existing one-use email token for initiating the backup download, while providing a separate short-lived resume authorization for the already-authorized local backup.

I would expect such a resume authorization to be narrowly scoped to the authenticated user and specific backup, and to permit a subsequent byte-range request without making the original one-day user-wide email token generally reusable.

The behaviours I’d aim to preserve/add are:

  • initial GET with a valid emailed token remains allowed;

  • another ordinary full GET using the consumed email token remains rejected;

  • a byte-range retry for the same backup by the same authenticated admin can be resumed for a short bounded period;

  • a resume attempt against another backup remains rejected;

  • an expired resume authorization remains rejected.

I haven’t assumed that this is necessarily the implementation maintainers will prefer, but the code/test surface appears fairly contained.

I’m happy to put together a draft patch and regression tests for the Safari Range: reproduction if this general security model sounds reasonable.