Restic sftp woes

I have a restic repository at /mnt/sandisk/backup on my router:

➜ tmp: echo "ls -la /mnt/sandisk/backup | sftp johannes@router
Connected to 192.168.2.1.
sftp> ls -la /mnt/sandisk/backup/
drwxr-xr-x ? johannes johannes 4096 Oct 10 23:03 /mnt/sandisk/backup/.
drwxr-xr-x ? root root 4096 Oct 10 22:29 /mnt/sandisk/backup/..
-r-------- ? johannes johannes 155 Oct 10 23:03 /mnt/sandisk/backup/config
drwx------ ? johannes johannes 4096 Oct 10 23:03 /mnt/sandisk/backup/data
drwx------ ? johannes johannes 4096 Oct 11 11:44 /mnt/sandisk/backup/index
drwx------ ? johannes johannes 4096 Oct 10 23:03 /mnt/sandisk/backup/keys
drwx------ ? johannes johannes 4096 Oct 11 11:48 /mnt/sandisk/backup/locks
drwx------ ? johannes johannes 4096 Oct 11 11:44 /mnt/sandisk/backup/snapshots

So far, so good. But then, when I try to use restic

➜ tmp: export RESTIC_REPOSITORY=sftp://johannes@192.168.2.1:/mnt/sandisk/backup/
➜ tmp: restic list snapshots
Fatal: unable to open config file: Lstat: file does not exist
Is there a repository at the following location?
sftp://johannes@192.168.2.1:/mnt/sandisk/backup/

What works instead is removing the // from the schema

➜ tmp: export RESTIC_REPOSITORY=sftp:johannes@192.168.2.1:/mnt/sandisk/backup/
➜ tmp: restic list snapshots
repository f41b4542 opened (version 2, compression level auto)
8a51a1d8beb68ce3a430d20c323c8bc0834b98cbc3dc313187bb4faf18c22dc5
c5f067c89ea2c41e4b52b29438dfa245d24a5ada267d43d57d4b7afb7c6af648
227df61c5d566f01543bc927f2bb5c1f37c043e396dfabec07ea373c716b0135
a5c403bdd8046df01529ce80182f7158c9273f26db9d94a351a9049d0a83e6b5
d3d521cbb027fd95713eff2d35ea63597778ed83c86fafc53d812f47266272da
a51c6fe9afb2639875f5a4d579f382b7a0b1dcba38fd466a3f0c834d5262f3a6

This, of course, took me about an hour to figure out.

Let's take a look at what is happening. You can produce debug logs by appending DEBUG_LOG=<filename> to the restic command:

➜ tmp: export RESTIC_REPOSITORY=sftp://johannes@192.168.2.1:/mnt/sandisk/backup/
➜ tmp: DEBUG_LOG=restic restic list snapshots
➜ tmp: cat restic
2025/10/11 11:51:37 restic/global.go:573 main.open 1 parsing location sftp://johannes@19
2.168.2.1:/mnt/sandisk/backup/
2025/10/11 11:51:37 restic/global.go:567 main.parseConfig 1 opening sftp repository at
&sftp.Config{User:"johannes", Host:"192.168.2.1", Port:"", Path:"mnt/sandisk/backup", Layout:"", Command:""
, Args:"", Connections:0x5}

Squint closely and you'll see Path:"mnt/sandisk/backup" - this is a relative path! In the other case

➜ tmp: export RESTIC_REPOSITORY=sftp:johannes@192.168.2.1:/mnt/sandisk/backup/
➜ tmp: DEBUG_LOG=restic restic list snapshots
➜ tmp: cat restic
2025/10/11 11:51:37 restic/global.go:573 main.open 1 parsing location sftp://johannes@19
2.168.2.1:/mnt/sandisk/backup/
2025/10/11 11:51:37 restic/global.go:567 main.parseConfig 1 opening sftp repository at
&sftp.Config{User:"johannes", Host:"192.168.2.1", Port:"", Path:"/mnt/sandisk/backup", Layout:"", Command:""
, Args:"", Connections:0x5}

Here's the absolute path we expect. And with that, it works!

So what was the problem? I wasn't reading the docs properly. Here it says

If you need to specify a port number or IPv6 address, you’ll need to use URL syntax. E.g., the repository /srv/restic-repo on [::1] (localhost) at port 2222 with username user can be specified as sftp://user@[::1]:2222//srv/restic-repo Note the double slash: the first slash separates the connection settings from the path, while the second is the start of the path. To specify a relative path, use one slash.

So, I was missing the double //, and my repository URL was being parsed as johannes@192.168.2.1:/mnt/sandisk/backup

port: ""

separator: "/"

path: "mnt/sandisk/backup"

Sigh. RTFM.