Restic iOS Client App

Hello,

I use restic to securely backup my data to cloud. I also found it would be useful to able to securely access my data any time. So I developed Restique https://apps.apple.com/app/restique/id6744624567, Securely access your Restic backup — anywhere, anytime on iOS! This app is not intended for backing up iOS device data. Instead, its primary purpose is to browse existing Restic backups directly on iOS. If that matches your needs, feel free to give the app a try. Feedback or comments are always welcome and appreciated!

Privacy by design: Restique stores all data encrypted and solely on the device, utilizing the operating system’s built-in data protection mechanisms NSFileProtectionComplete. Repository-related data, such as repository URL, password, and env variables, are further encrypted and securely stored in the device’s Keychain, ensuring maximum security and privacy.

7 Likes

I have been WAITING for this!! Will check out shortly!

EDIT: Damn, only supports REST, S3, and B2. My repos are on SSH, WebDAV, and SharePoint. :frowning:

Hmm in the example it says SSH:// - does SSH work?? I don’t see it in the repo type.

1 Like

Thanks for your feedback. Unfortunately SSH requires fork which iOS not support. I haven’t explore backend through rclone yet.

1 Like

OP, this looks great. But I’ll need you to provide convincing arguments that privacy and security are taken really serious by you / your app. I see you also have a Borg backup browser, which denotes you have an interest in this area which is definitely in your favour.

By no means I’m criticizing (on the contrary, I commend you for doing this), but you have to understand installing a one-month old app that claims to be “private and secure” is not very convincing

Please convince us we can trust you :slight_smile:

Hello @wally_walrus, thank for asking this question.
Yes, We take security and privacy seriously—they are our top priorities. You can review Privacy Policy and Threat Modeling included in app About page. Feel free to ask more question we can clarify it.

Here is highlights for data security:

Restique stores all data encrypted and solely on the device, utilizing the operating system’s built-in data protection mechanisms. Repository-related data, such as repository URL, passphrase, and SSH keys, are further encrypted and securely stored in the device’s Keychain, ensuring maximum security and privacy.

No data is collected:

Restique does not pass on any data to third parties, is completely free of advertising and does not use analytics software to monitor user behavior. However, some functionalities can only be provided by using external data sources, frameworks or operating system services, which in turn process data and are subject to a separate privacy policy. We don’t have such functionalities yet.

You can verify which servers the app communicates with by going to System Settings > App Privacy Report > App Network Activity, then selecting Restique. This will show all the domains contacted by the app. You should see only your backup server’s domain. Some subdomains from apple.com may appear due to system functions, but no other third-party domains should be listed.

1 Like

Just added WebDAV support.

2 Likes

Hmmm, I’m getting “The data can’t be read because it’s missing.”

I did webdav:https://user:pass@user.your-storagebox.de/restic-db which works on MacOS at least. My password is ~64 characters long, in case maybe it’s being truncated…?

It does not truncate password, does your password have special characters like :@/ ?
Can you try following command to test webdav connection?

curl -s -u user:pass -X PROPFIND -H ‘Depth: 1’ https://user.your-storagebox.de/restic-db

My password is alphanumeric only, no special characters. Ran into the special character problem early on, and just made it long as hell instead :slight_smile:

For a second I thought we had it. If I didn’t use a trailing / I got an error message. If I used a trailing / it seemed to work. So I put one in the app and… same error message.

Perhaps Hetzner is fussy about trailing slashes, and your app disregards trailing slashes?? :man_shrugging:t2:


curl -s -u username:password -X PROPFIND -H ‘Depth: 1’ https://username.your-storagebox.de/restic-db

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://username.your-storagebox.de/restic-db/">here</a>.</p>
<hr>
<address>Apache Server at username.your-storagebox.de Port 443</address>
</body></html>

curl -s -u username:password -X PROPFIND -H ‘Depth: 1’ https://username.your-storagebox.de/restic-db/

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-07-15T22:00:27Z</lp1:creationdate>
<lp1:getlastmodified>Tue, 15 Jul 2025 22:00:27 GMT</lp1:getlastmodified>
<lp1:getetag>"a-639fee76745a1"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/locks/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-07-03T06:36:14Z</lp1:creationdate>
<lp1:getlastmodified>Thu, 03 Jul 2025 06:36:14 GMT</lp1:getlastmodified>
<lp1:getetag>"4-63900980fedc8"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/.DS_Store</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2025-01-02T02:23:52Z</lp1:creationdate>
<lp1:getcontentlength>6148</lp1:getcontentlength>
<lp1:getlastmodified>Thu, 02 Jan 2025 02:23:52 GMT</lp1:getlastmodified>
<lp1:getetag>"1804-62aafda66559f"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/._config</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2025-07-15T22:00:27Z</lp1:creationdate>
<lp1:getcontentlength>4096</lp1:getcontentlength>
<lp1:getlastmodified>Tue, 15 Jul 2025 22:00:27 GMT</lp1:getlastmodified>
<lp1:getetag>"1000-639fee76741b9"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/config</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2024-07-23T04:55:53Z</lp1:creationdate>
<lp1:getcontentlength>155</lp1:getcontentlength>
<lp1:getlastmodified>Thu, 18 May 2023 16:17:50 GMT</lp1:getlastmodified>
<lp1:getetag>"9b-5fbfa2550c780"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/data/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-01-02T04:56:51Z</lp1:creationdate>
<lp1:getlastmodified>Thu, 02 Jan 2025 04:56:51 GMT</lp1:getlastmodified>
<lp1:getetag>"103-62ab1fd86606a"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/snapshots/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-07-03T06:36:14Z</lp1:creationdate>
<lp1:getlastmodified>Thu, 03 Jul 2025 06:36:14 GMT</lp1:getlastmodified>
<lp1:getetag>"99-63900980d03b4"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/keys/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-03-09T11:06:40Z</lp1:creationdate>
<lp1:getlastmodified>Sun, 09 Mar 2025 11:06:40 GMT</lp1:getlastmodified>
<lp1:getetag>"4-62fe6d9a14e72"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/restic-db/index/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2025-07-03T06:36:13Z</lp1:creationdate>
<lp1:getlastmodified>Thu, 03 Jul 2025 06:36:13 GMT</lp1:getlastmodified>
<lp1:getetag>"142-6390097fe55f7"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>

Thanks for sharing this. I tested other webdav backend didn’t run into this kind of issue. I don’t use Hetzner, will register a account to test their webdav behavior.

I think you’ll have to pay for a Storage Box to get WebDAV access. I will make a test folder and a sub-account granting access to said test folder, and DM you the credentials. Please hold! :slight_smile:

EDIT: Credentials sent!

Just a follow-up, @flinteger-code and I got it working, though some stats won’t load just because my repo is so huge and my phone doesn’t have enough memory.

But what does work is awesome and exactly what I’ve been looking for! You get all your snapshots, can traverse the tree, search, restore whatever you’d like. If you’re on iPhone you’ll want to disable the automatic screen lock, as it might take more than 5 mins at times - but it works! Very slick!

1 Like

Thank you @akrabu for your detailed feedback to make this app can handle snapshots from old version of restic (back to 2010).

If you encounter any issues or have an new requirement, please let me know so we can resolve them.

1 Like

Saw the new update, wondered if I needed to add a new repo or just refresh my existing one to get the repo size and files count. Pulled down to refresh. It spun at the top for a few minutes, then stopped spinning. Stayed like that for about 40 minutes then crashed. However, while I was waiting, I checked the (i) and yes indeed it did update the stats - before it crashed even haha. Pretty cool - though since it’s done this way, older snapshots don’t count, so my repo seems way smaller than it actually is. But makes sense for newer repos going forward. Not much else you can do with the memory restrictions of phones anyway.

Ps. One teensy little thing I noticed is that the Compressed Size and the Total Files numbers aren’t aligned quite right - looks like maybe the “,” is making the Total Files align just a little bit higher. Just cosmetic but I thought you’d want to know :slight_smile:

Thanks for trying out the new version! It calculates stats based on existing snapshot data, so there’s no need to refresh snapshots or re-add the repository. The font issue was fixed in build 43.

1 Like

Hi @flinteger-code quick question… my restic backup is normally run on my Mac and using a password saved in the keychain (but not as a password, ie using the “security” CLI command instead). I have my passwords and keychain saved to iCloud, so presumably the same password is available to my iPhone. Is your app capable of using passwords from the keychain (not only from the Passwords app) to decrypt and open the backup?

Thanks for your work on this

Hello @wally_walrus On iPhone, password autofill requires manual interaction with the password manager. For security reasons, it’s not possible to access iCloud Keychain without explicitly prompting the user. Restique needs the repository password to decrypt the backup. That password is encrypted and securely stored in the local keychain to ensure your data remains protected.

You’re welcome! I also use restic for my own backups and rely on this app to access my data securely. The data protection mechanisms were a priority from the very beginning of the design phase. You can refer AboutThreat Modeling for further info. If it weren’t secure, I wouldn’t use it myself.

Thanks! Prompting for the password would be fine if there was an option to not be stored. Storing it means I’ll have the same password stored twice in the shared keychain, unless I’m confused about the functionality of the iCloud-shared keychain

Restique uses the local keychain, which is only accessible by the app on the device—it is not shared with iCloud Keychain. Once you save a password in the app, there’s no way to view or extract it. It’s used solely when interacting with your backup to decrypt data.

If you need to update the password, please note that there’s currently no way to update it directly due to security measurement. You’ll need to delete the repository in the app and re-add it using the new password.

I just made a new release of Restique 1.7.0. It includes a few improvements like show tag info, view snapshot items like a directory tree (by default it is a flat list) and fixes to compatible with old snapshots data format. Really appreciated @akrabu’s feedback to make this happen.

2 Likes