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.