diff --git a/Package.swift b/Package.swift
index 617ab061294b661012d646258bf8cde4c3cf4aef..0e7bb614d299a3ed2eadd9464518af68ad563f9f 100644
--- a/Package.swift
+++ b/Package.swift
@@ -27,6 +27,7 @@ let package = Package(
         .library(name: "Integration", targets: ["Integration"]),
         .library(name: "ChatFeature", targets: ["ChatFeature"]),
         .library(name: "PushFeature", targets: ["PushFeature"]),
+        .library(name: "SFTPFeature", targets: ["SFTPFeature"]),
         .library(name: "CrashService", targets: ["CrashService"]),
         .library(name: "Presentation", targets: ["Presentation"]),
         .library(name: "BackupFeature", targets: ["BackupFeature"]),
@@ -68,6 +69,7 @@ let package = Package(
         .package(url: "https://github.com/google/google-api-objectivec-client-for-rest", from: "1.6.0"),
         .package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.5")),
         .package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")),
+        .package(url: "https://github.com/darrarski/Shout.git", revision: "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"),
         .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.0"))
     ],
     targets: [
@@ -81,6 +83,7 @@ let package = Package(
                 "ChatFeature",
                 "MenuFeature",
                 "PushFeature",
+                "SFTPFeature",
                 "ToastFeature",
                 "CrashService",
                 "BackupFeature",
@@ -208,6 +211,23 @@ let package = Package(
                 ]
             ),
 
+        // MARK: - SFTPFeature
+
+            .target(
+                name: "SFTPFeature",
+                dependencies: [
+                    "HUD",
+                    "Shared",
+                    "Keychain",
+                    "InputField",
+                    "DependencyInjection",
+                    .product(
+                        name: "Shout",
+                        package: "Shout"
+                    )
+                ]
+            ),
+
         // MARK: - GoogleDriveFeature
 
             .target(
@@ -398,6 +418,7 @@ let package = Package(
                 dependencies: [
                     "HUD",
                     "Shared",
+                    "SFTPFeature",
                     "Integration",
                     "Presentation",
                     "iCloudFeature",
@@ -612,6 +633,7 @@ let package = Package(
                     "Shared",
                     "Models",
                     "InputField",
+                    "SFTPFeature",
                     "Presentation",
                     "iCloudFeature",
                     "DrawerFeature",
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index 3675b45050f041af457b16d9bc6105633dabab91..72a7f682477967599a823601b8315c46ed429ee6 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -1,8 +1,8 @@
 import UIKit
 import BackgroundTasks
 
-import XXModels
 import Theme
+import XXModels
 import XXLogger
 import Defaults
 import Integration
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 2a886995871ac2b82dd2fb0c5e01b1b0187a71bb..87b8e7dd9d903fbf11f686ac46ecb730fac4eef7 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -18,6 +18,7 @@ import Voxophone
 import Integration
 import Permissions
 import PushFeature
+import SFTPFeature
 import CrashService
 import ToastFeature
 import iCloudFeature
@@ -63,6 +64,7 @@ struct DependencyRegistrator {
 
         /// Restore / Backup
 
+        container.register(SFTPService.mock)
         container.register(iCloudServiceMock() as iCloudInterface)
         container.register(DropboxServiceMock() as DropboxInterface)
         container.register(GoogleDriveServiceMock() as GoogleDriveInterface)
@@ -86,6 +88,7 @@ struct DependencyRegistrator {
 
         /// Restore / Backup
 
+        container.register(SFTPService.live)
         container.register(iCloudService() as iCloudInterface)
         container.register(DropboxService() as DropboxInterface)
         container.register(GoogleDriveService() as GoogleDriveInterface)
diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift
index c61bd74d6c971c544087d7020db72ce16d11e763..a901e6b9668fc89df2cd1ae1184ca655fa8bdb1b 100644
--- a/Sources/BackupFeature/Controllers/BackupConfigController.swift
+++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift
@@ -105,6 +105,11 @@ final class BackupConfigController: UIViewController {
             .sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
             .store(in: &cancellables)
 
+        screenView.sftpButton.switcherView
+            .publisher(for: .valueChanged)
+            .sink { [unowned self] in viewModel.didToggleService(self, .sftp, screenView.sftpButton.switcherView.isOn) }
+            .store(in: &cancellables)
+
         screenView.iCloudButton.switcherView
             .publisher(for: .valueChanged)
             .sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
@@ -115,6 +120,11 @@ final class BackupConfigController: UIViewController {
             .sink { [unowned self] in viewModel.didTapService(.dropbox, self) }
             .store(in: &cancellables)
 
+        screenView.sftpButton
+            .publisher(for: .touchUpInside)
+            .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
+            .store(in: &cancellables)
+
         screenView.iCloudButton
             .publisher(for: .touchUpInside)
             .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
@@ -128,16 +138,17 @@ final class BackupConfigController: UIViewController {
         case .none:
             break
         case .icloud:
-            serviceName = "iCloud"
+            serviceName = Localized.Backup.iCloud
             button = screenView.iCloudButton
-
         case .dropbox:
-            serviceName = "Dropbox"
+            serviceName = Localized.Backup.dropbox
             button = screenView.dropboxButton
-
         case .drive:
-            serviceName = "Google Drive"
+            serviceName = Localized.Backup.googleDrive
             button = screenView.googleDriveButton
+        case .sftp:
+            serviceName = Localized.Backup.sftp
+            button = screenView.sftpButton
         }
 
         screenView.enabledSubtitleLabel.text
@@ -146,10 +157,12 @@ final class BackupConfigController: UIViewController {
         = Localized.Backup.Config.frequency(serviceName).uppercased()
 
         guard let button = button else {
+            screenView.sftpButton.isHidden = false
             screenView.iCloudButton.isHidden = false
             screenView.dropboxButton.isHidden = false
             screenView.googleDriveButton.isHidden = false
 
+            screenView.sftpButton.switcherView.isOn = false
             screenView.iCloudButton.switcherView.isOn = false
             screenView.dropboxButton.switcherView.isOn = false
             screenView.googleDriveButton.switcherView.isOn = false
@@ -166,11 +179,13 @@ final class BackupConfigController: UIViewController {
         screenView.latestBackupDetailView.isHidden = false
         screenView.infrastructureDetailView.isHidden = false
 
-        [screenView.iCloudButton, screenView.dropboxButton, screenView.googleDriveButton]
-            .forEach {
-                $0.isHidden = $0 != button
-                $0.switcherView.isOn = $0 == button
-            }
+        [screenView.iCloudButton,
+         screenView.dropboxButton,
+         screenView.googleDriveButton,
+         screenView.sftpButton].forEach {
+            $0.isHidden = $0 != button
+            $0.switcherView.isOn = $0 == button
+        }
     }
 
     private func decorate(connectedServices: Set<CloudService>) {
@@ -191,6 +206,12 @@ final class BackupConfigController: UIViewController {
         } else {
             screenView.googleDriveButton.showChevron()
         }
+
+        if connectedServices.contains(.sftp) {
+            screenView.sftpButton.showSwitcher(enabled: false)
+        } else {
+            screenView.sftpButton.showChevron()
+        }
     }
 
     private func presentInfrastructureDrawer(wifiOnly: Bool) {
diff --git a/Sources/BackupFeature/Controllers/BackupSetupController.swift b/Sources/BackupFeature/Controllers/BackupSetupController.swift
index 49e05a2bd74ce9141e19320cfebbc33f989414f7..e22de517680d7ab5fc348b56cbb396ca80c4e091 100644
--- a/Sources/BackupFeature/Controllers/BackupSetupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupSetupController.swift
@@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController {
             .publisher(for: .touchUpInside)
             .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
             .store(in: &cancellables)
+
+        screenView.sftpButton
+            .publisher(for: .touchUpInside)
+            .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
+            .store(in: &cancellables)
     }
 }
diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
index 9dbd110e659f066dd25d5bda897ebccc14047748..a49b51bd28fe3a670efeba3c2c9cff8e34e31af6 100644
--- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
+++ b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
@@ -18,16 +18,10 @@ public protocol BackupCoordinating {
 public struct BackupCoordinator: BackupCoordinating {
     var bottomPresenter: Presenting = BottomPresenter()
 
-    var passphraseFactory: (
-        @escaping EmptyClosure,
-        @escaping StringClosure
-    ) -> UIViewController
+    var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
 
     public init(
-        passphraseFactory: @escaping (
-            @escaping EmptyClosure,
-            @escaping StringClosure
-        ) -> UIViewController
+        passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
     ) {
         self.passphraseFactory = passphraseFactory
     }
diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index 8b82a8f27c40af85acd836182144d05c945008e1..626adba515d28c1a9969b067da742d07e94c8da5 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -2,6 +2,8 @@ import UIKit
 import Models
 import Combine
 import Defaults
+import Keychain
+import SFTPFeature
 import iCloudFeature
 import DropboxFeature
 import NetworkMonitor
@@ -9,10 +11,12 @@ import GoogleDriveFeature
 import DependencyInjection
 
 public final class BackupService {
+    @Dependency private var sftpService: SFTPService
     @Dependency private var icloudService: iCloudInterface
     @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var driveService: GoogleDriveInterface
     @Dependency private var networkManager: NetworkMonitoring
+    @Dependency private var keychainHandler: KeychainHandling
+    @Dependency private var driveService: GoogleDriveInterface
 
     @KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data
 
@@ -149,6 +153,15 @@ extension BackupService {
                         self.refreshBackups()
                     }.store(in: &cancellables)
             }
+        case .sftp:
+            if !sftpService.isAuthorized() {
+                sftpService.authorizeFlow((screen, { [weak self] in
+                    guard let self = self else { return }
+                    screen.navigationController?.popViewController(animated: true)
+                    self.refreshConnections()
+                    self.refreshBackups()
+                }))
+            }
         }
     }
 }
@@ -167,6 +180,12 @@ extension BackupService {
             settings.value.connectedServices.remove(.dropbox)
         }
 
+        if sftpService.isAuthorized() && !settings.value.connectedServices.contains(.sftp) {
+            settings.value.connectedServices.insert(.sftp)
+        } else if !sftpService.isAuthorized() && settings.value.connectedServices.contains(.sftp) {
+            settings.value.connectedServices.remove(.sftp)
+        }
+
         driveService.isAuthorized { [weak settings] isAuthorized in
             guard let settings = settings else { return }
 
@@ -196,6 +215,23 @@ extension BackupService {
             }
         }
 
+        if sftpService.isAuthorized() {
+            sftpService.fetchMetadata { [weak settings] in
+                guard let settings = settings else { return }
+
+                guard let metadata = try? $0.get()?.backup else {
+                    settings.value.backups[.sftp] = nil
+                    return
+                }
+
+                settings.value.backups[.sftp] = Backup(
+                    id: metadata.id,
+                    date: metadata.date,
+                    size: metadata.size
+                )
+            }
+        }
+
         if dropboxService.isAuthorized() {
             dropboxService.downloadMetadata { [weak settings] in
                 guard let settings = settings else { return }
@@ -241,7 +277,7 @@ extension BackupService {
             .appendingPathComponent(UUID().uuidString)
 
         do {
-            try data.write(to: url)
+            try data.write(to: url, options: .atomic)
         } catch {
             print("Couldn't write to temp: \(error.localizedDescription)")
             return
@@ -260,8 +296,6 @@ extension BackupService {
                 case .failure(let error):
                     print(error.localizedDescription)
                 }
-
-                // try? FileManager.default.removeItem(at: url)
             }
         case .icloud:
             icloudService.uploadBackup(url) {
@@ -275,8 +309,6 @@ extension BackupService {
                 case .failure(let error):
                     print(error.localizedDescription)
                 }
-
-                // try? FileManager.default.removeItem(at: url)
             }
         case .dropbox:
             dropboxService.uploadBackup(url) {
@@ -290,8 +322,15 @@ extension BackupService {
                 case .failure(let error):
                     print(error.localizedDescription)
                 }
-
-                // try? FileManager.default.removeItem(at: url)
+            }
+        case .sftp:
+            sftpService.uploadBackup(url: url) {
+                switch $0 {
+                case .success(let backup):
+                    self.settings.value.backups[.sftp] = backup
+                case .failure(let error):
+                    print(error.localizedDescription)
+                }
             }
         }
     }
diff --git a/Sources/BackupFeature/Views/BackupConfigView.swift b/Sources/BackupFeature/Views/BackupConfigView.swift
index 8c400b3ff9162f380548d0478f2356454910833e..1c65f58f7222f1c069284c717518904dc2e596ca 100644
--- a/Sources/BackupFeature/Views/BackupConfigView.swift
+++ b/Sources/BackupFeature/Views/BackupConfigView.swift
@@ -7,6 +7,7 @@ final class BackupConfigView: UIView {
     let actionView = BackupActionView()
 
     let stackView = UIStackView()
+    let sftpButton = BackupSwitcherButton()
     let iCloudButton = BackupSwitcherButton()
     let dropboxButton = BackupSwitcherButton()
     let googleDriveButton = BackupSwitcherButton()
@@ -44,6 +45,9 @@ final class BackupConfigView: UIView {
         subtitleLabel.numberOfLines = 0
         subtitleLabel.attributedText = attString
 
+        sftpButton.titleLabel.text = Localized.Backup.sftp
+        sftpButton.logoImageView.image = Asset.restoreSFTP.image
+
         iCloudButton.titleLabel.text = Localized.Backup.iCloud
         iCloudButton.logoImageView.image = Asset.restoreIcloud.image
 
@@ -65,6 +69,7 @@ final class BackupConfigView: UIView {
         stackView.addArrangedSubview(googleDriveButton)
         stackView.addArrangedSubview(iCloudButton)
         stackView.addArrangedSubview(dropboxButton)
+        stackView.addArrangedSubview(sftpButton)
         stackView.addArrangedSubview(enabledSubtitleView)
         stackView.addArrangedSubview(latestBackupDetailView)
         stackView.addArrangedSubview(frequencyDetailView)
@@ -75,36 +80,36 @@ final class BackupConfigView: UIView {
         addSubview(actionView)
         addSubview(stackView)
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(15)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        enabledSubtitleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(-10)
-            make.left.equalToSuperview().offset(92)
-            make.right.equalToSuperview().offset(-48)
-            make.bottom.equalToSuperview()
+        enabledSubtitleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(-10)
+            $0.left.equalToSuperview().offset(92)
+            $0.right.equalToSuperview().offset(-48)
+            $0.bottom.equalToSuperview()
         }
 
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        subtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        actionView.snp.makeConstraints { make in
-            make.top.equalTo(subtitleLabel.snp.bottom).offset(15)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
+        actionView.snp.makeConstraints {
+            $0.top.equalTo(subtitleLabel.snp.bottom).offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-38)
         }
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(actionView.snp.bottom).offset(28)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualToSuperview()
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(actionView.snp.bottom).offset(28)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.lessThanOrEqualToSuperview()
         }
     }
 
diff --git a/Sources/BackupFeature/Views/BackupSetupView.swift b/Sources/BackupFeature/Views/BackupSetupView.swift
index eeaad48195da765ac9833293ccd7f7f9a7673ca0..3e19d50034f4d03ba62d3e4d038d82d24196af89 100644
--- a/Sources/BackupFeature/Views/BackupSetupView.swift
+++ b/Sources/BackupFeature/Views/BackupSetupView.swift
@@ -6,6 +6,7 @@ final class BackupSetupView: UIView {
     let subtitleLabel = UILabel()
 
     let stackView = UIStackView()
+    let sftpButton = BackupSwitcherButton()
     let iCloudButton = BackupSwitcherButton()
     let dropboxButton = BackupSwitcherButton()
     let googleDriveButton = BackupSwitcherButton()
@@ -60,32 +61,37 @@ final class BackupSetupView: UIView {
         googleDriveButton.logoImageView.image = Asset.restoreDrive.image
         googleDriveButton.showChevron()
 
+        sftpButton.titleLabel.text = Localized.Backup.sftp
+        sftpButton.logoImageView.image = Asset.restoreSFTP.image
+        sftpButton.showChevron()
+
         stackView.axis = .vertical
         stackView.addArrangedSubview(googleDriveButton)
         stackView.addArrangedSubview(iCloudButton)
         stackView.addArrangedSubview(dropboxButton)
+        stackView.addArrangedSubview(sftpButton)
 
         addSubview(titleLabel)
         addSubview(subtitleLabel)
         addSubview(stackView)
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(15)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        subtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(subtitleLabel.snp.bottom).offset(28)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualToSuperview()
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.lessThanOrEqualToSuperview()
         }
     }
 
diff --git a/Sources/InputField/OutlinedInputField.swift b/Sources/InputField/OutlinedInputField.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3bb8ad3005167b9e9e99511243da912f7082d434
--- /dev/null
+++ b/Sources/InputField/OutlinedInputField.swift
@@ -0,0 +1,85 @@
+import UIKit
+import Shared
+import Combine
+
+public final class OutlinedInputField: UIView {
+    private let stackView = UIStackView()
+    private let textField = UITextField()
+    private let placeholderLabel = UILabel()
+    private let inputContainerView = UIView()
+
+    private let secureInputButton = SecureInputButton()
+
+    public var textPublisher: AnyPublisher<String, Never> {
+        textField.textPublisher
+    }
+
+    public init() {
+        super.init(frame: .zero)
+
+        layer.borderWidth = 1.0
+        layer.cornerRadius = 4.0
+        layer.masksToBounds = true
+        layer.borderColor = Asset.neutralWeak.color.cgColor
+
+        textField.delegate = self
+        textField.backgroundColor = .clear
+        textField.textColor = Asset.neutralDark.color
+        placeholderLabel.textColor = Asset.neutralWeak.color
+        placeholderLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+
+        secureInputButton.button.addTarget(self, action: #selector(didTapRight), for: .touchUpInside)
+
+        inputContainerView.addSubview(placeholderLabel)
+        inputContainerView.addSubview(textField)
+
+        stackView.addArrangedSubview(inputContainerView)
+        stackView.addArrangedSubview(secureInputButton)
+
+        addSubview(stackView)
+
+        placeholderLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(15)
+            $0.left.equalToSuperview().offset(15)
+            $0.right.lessThanOrEqualToSuperview().offset(-15)
+            $0.bottom.equalToSuperview().offset(-18)
+        }
+
+        textField.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(15)
+            $0.left.equalToSuperview().offset(15)
+            $0.right.equalToSuperview().offset(-15)
+            $0.bottom.equalToSuperview().offset(-18)
+        }
+
+        stackView.snp.makeConstraints {
+            $0.edges.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    public func setup(title: String, sensitive: Bool = false) {
+        placeholderLabel.text = title
+        textField.isSecureTextEntry = sensitive
+        secureInputButton.isHidden = !sensitive
+    }
+
+    @objc private func didTapRight() {
+        textField.isSecureTextEntry.toggle()
+        secureInputButton.setSecure(textField.isSecureTextEntry)
+    }
+}
+
+extension OutlinedInputField: UITextFieldDelegate {
+    public func textField(
+        _ textField: UITextField,
+        shouldChangeCharactersIn range: NSRange,
+        replacementString string: String
+    ) -> Bool {
+        placeholderLabel.alpha = (textField.text! as NSString)
+            .replacingCharacters(in: range, with: string)
+            .count > 0 ? 0.0 : 1.0
+        return true
+    }
+}
diff --git a/Sources/InputField/SecureInputButton.swift b/Sources/InputField/SecureInputButton.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1f2e6b20751370755ec23b966c153ca5440c9f32
--- /dev/null
+++ b/Sources/InputField/SecureInputButton.swift
@@ -0,0 +1,31 @@
+import UIKit
+import Shared
+
+final class SecureInputButton: UIView {
+    private(set) var button = UIButton()
+    private let color = Asset.neutralSecondaryAlternative.color
+    private lazy var openedImage = Asset.eyeOpen.image.withTintColor(color)
+    private lazy var closedImage = Asset.eyeClosed.image.withTintColor(color)
+
+    init() {
+        super.init(frame: .zero)
+
+        button.setContentCompressionResistancePriority(.required, for: .horizontal)
+        button.setImage(Asset.eyeClosed.image.withTintColor(color), for: .normal)
+
+        addSubview(button)
+
+        button.snp.makeConstraints {
+            $0.top.equalToSuperview()
+            $0.left.equalToSuperview().offset(10)
+            $0.right.equalToSuperview().offset(-10)
+            $0.bottom.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    func setSecure(_ bool: Bool) {
+        button.setImage(bool ? closedImage : openedImage, for: .normal)
+    }
+}
diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift
index 7d53fb7efaf8ebe3b4f6485bc38dd101ce8960e5..2b488ef925bd1d5367b94585dc4959bf14b66ea6 100644
--- a/Sources/Integration/Client.swift
+++ b/Sources/Integration/Client.swift
@@ -89,7 +89,12 @@ public class Client {
     //    }
 
     public func addJson(_ string: String) {
-        guard let backupManager = backupManager else { return }
+        guard let backupManager = backupManager else {
+            fatalError()
+        }
+
+        print("^^^ Set params: \(string) to backup")
+
         backupManager.addJson(string)
     }
 
diff --git a/Sources/Integration/Implementations/Bindings.swift b/Sources/Integration/Implementations/Bindings.swift
index 6aac71d4e5c02c661c719db62816662286f9c308..7c6feb9f10688bc0d34726aec2479c8174438484 100644
--- a/Sources/Integration/Implementations/Bindings.swift
+++ b/Sources/Integration/Implementations/Bindings.swift
@@ -499,11 +499,11 @@ extension BindingsClient: BindingsInterface {
             fatalError("Couldn't retrieve cert.")
         }
 
-        try! udb!.setAlternative(
-            "18.198.117.203:11420".data(using: .utf8),
-            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
-            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
-        )
+//        try! udb!.setAlternative(
+//            "18.198.117.203:11420".data(using: .utf8),
+//            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
+//            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
+//        )
 
         guard let error = error else { return udb! }
         throw error.friendly()
@@ -525,11 +525,11 @@ extension BindingsClient: BindingsInterface {
             fatalError("Couldn't retrieve cert.")
         }
 
-        try! udb!.setAlternative(
-            "18.198.117.203:11420".data(using: .utf8),
-            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
-            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
-        )
+//        try! udb!.setAlternative(
+//            "18.198.117.203:11420".data(using: .utf8),
+//            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
+//            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
+//        )
 
         guard let error = error else { return udb! }
         throw error.friendly()
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index ae01b853ac2ac24d7e8427f3a868dcb18b34dcde..51234cabf4aa2f29f29bc41bfc4166cd5afa2ab9 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -356,6 +356,11 @@ public final class Session: SessionType {
         ).jsonFormat
 
         client.addJson(params)
+
+        guard username!.isEmpty == false else {
+            fatalError("Tried to build a backup with my username but an empty string was set to it")
+        }
+
         backupService.performBackupIfAutomaticIsEnabled()
     }
 
@@ -381,7 +386,6 @@ public final class Session: SessionType {
             .store(in: &cancellables)
 
         client.backup
-            .throttle(for: .seconds(5), scheduler: DispatchQueue.main, latest: true)
             .sink { [unowned self] in backupService.updateBackup(data: $0) }
             .store(in: &cancellables)
 
@@ -402,7 +406,6 @@ public final class Session: SessionType {
                 if $0 == true {
                     guard let passphrase = backupService.passphrase else {
                         client.resumeBackup()
-                        updateFactsOnBackup()
                         return
                     }
 
@@ -416,10 +419,6 @@ public final class Session: SessionType {
             }
             .store(in: &cancellables)
 
-        networkMonitor.statusPublisher
-            .sink { print($0) }
-            .store(in: &cancellables)
-
         client.messages
             .sink { [unowned self] in
                 if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
diff --git a/Sources/Keychain/KeychainHandler.swift b/Sources/Keychain/KeychainHandler.swift
index 23f248879a7f2275b0faddd9f4c08962d9ed6246..6ac0d645d6def33360ee5ed0d0ab1e973a0487fc 100644
--- a/Sources/Keychain/KeychainHandler.swift
+++ b/Sources/Keychain/KeychainHandler.swift
@@ -1,15 +1,24 @@
 import Foundation
 import KeychainAccess
 
+public enum KeychainSFTP: String {
+    case pwd
+    case host
+    case username
+}
+
 public protocol KeychainHandling {
     func clear() throws
     func getPassword() throws -> Data?
     func store(password pwd: Data) throws
+
+    func get(key: KeychainSFTP) throws -> String?
+    func store(key: KeychainSFTP, value: String) throws
 }
 
 public struct KeychainHandler: KeychainHandling {
-    private let password = "password"
     private let keychain: Keychain
+    private let password = "password"
 
     public init() {
         self.keychain = Keychain(service: "XXM")
@@ -26,4 +35,12 @@ public struct KeychainHandler: KeychainHandling {
     public func getPassword() throws -> Data? {
         try keychain.getData(password)
     }
+
+    public func get(key: KeychainSFTP) throws -> String? {
+        try keychain.get(key.rawValue)
+    }
+
+    public func store(key: KeychainSFTP, value: String) throws {
+        try keychain.set(value, key: key.rawValue)
+    }
 }
diff --git a/Sources/Keychain/MockKeychainHandler.swift b/Sources/Keychain/MockKeychainHandler.swift
index c1ff10dd802d5cd230cffbe25f20b744aa06126b..39d4a33ddb1e0e6d8a75d8b5a1ea980d8fb189bf 100644
--- a/Sources/Keychain/MockKeychainHandler.swift
+++ b/Sources/Keychain/MockKeychainHandler.swift
@@ -6,4 +6,6 @@ public struct MockKeychainHandler: KeychainHandling {
     public func clear() throws {}
     public func store(password pwd: Data) throws {}
     public func getPassword() throws -> Data? { Data() }
+    public func get(key: KeychainSFTP) throws -> String? { nil }
+    public func store(key: KeychainSFTP, value: String) throws {}
 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 054432b4403e7177bf129c21680a58ac4abb6b12..4e1b25ea61eb4475c9849f1873482b8a6eec3b25 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -5,6 +5,7 @@ import Models
 import Combine
 import Defaults
 import XXModels
+import Keychain
 import Foundation
 import Integration
 import Permissions
@@ -31,6 +32,7 @@ final class LaunchViewModel {
     @Dependency private var network: XXNetworking
     @Dependency private var versionChecker: VersionChecker
     @Dependency private var dropboxService: DropboxInterface
+    @Dependency private var keychainHandler: KeychainHandling
     @Dependency private var permissionHandler: PermissionHandling
 
     @KeyObject(.username, defaultValue: nil) var username: String?
@@ -90,6 +92,7 @@ final class LaunchViewModel {
                     self.hudSubject.send(.none)
                     self.routeSubject.send(.onboarding(ndf))
                     self.dropboxService.unlink()
+                    try? self.keychainHandler.clear()
                     return
                 }
 
@@ -98,6 +101,7 @@ final class LaunchViewModel {
                     self.hudSubject.send(.none)
                     self.routeSubject.send(.onboarding(ndf))
                     self.dropboxService.unlink()
+                    try? self.keychainHandler.clear()
                     return
                 }
 
diff --git a/Sources/Models/CloudService.swift b/Sources/Models/CloudService.swift
index 5daba7238d74388dac591b63220dbbc8b1c9f911..d217dca853e6ecf22f119520d9805567f7ead3a5 100644
--- a/Sources/Models/CloudService.swift
+++ b/Sources/Models/CloudService.swift
@@ -2,4 +2,5 @@ public enum CloudService: Equatable, Codable {
     case drive
     case icloud
     case dropbox
+    case sftp
 }
diff --git a/Sources/NetworkMonitor/MockNetworkMonitor.swift b/Sources/NetworkMonitor/MockNetworkMonitor.swift
index 473eb593ece68fd213a4896abe9a74eb3c23d286..30bf846df9e36359ed80a7572a966e56091dce4d 100644
--- a/Sources/NetworkMonitor/MockNetworkMonitor.swift
+++ b/Sources/NetworkMonitor/MockNetworkMonitor.swift
@@ -32,11 +32,11 @@ public struct MockNetworkMonitor: NetworkMonitoring {
         statusRelay.send(status)
 
         if status == .available {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
                 simulateOscilation(.internetNotAvailable)
             }
         } else if status == .internetNotAvailable {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                 simulateOscilation(.available)
             }
         }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
index 19fc945b63b2495f5823af11fb21de205f538d85..d25db4581f5c5dbd3083515b1828433494631e45 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
@@ -1,9 +1,9 @@
 import HUD
-import DrawerFeature
 import Theme
 import UIKit
 import Shared
 import Combine
+import DrawerFeature
 import DependencyInjection
 import ScrollViewController
 
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index 041717b6dde907568db4872827957d487010d006..c73e94edec314484dc06307f84e9e528f66bcc2a 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -1,8 +1,8 @@
 import HUD
-import DrawerFeature
-import Shared
 import UIKit
+import Shared
 import Combine
+import DrawerFeature
 import DependencyInjection
 
 public final class RestoreListController: UIViewController {
@@ -51,15 +51,16 @@ public final class RestoreListController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hud
+        viewModel.hudPublisher
             .receive(on: DispatchQueue.main)
             .sink { [hud] in hud.update(with: $0) }
             .store(in: &cancellables)
 
-        viewModel.didFetchBackup
+        viewModel.backupPublisher
             .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toRestore(using: ndf, with: $0, from: self) }
-            .store(in: &cancellables)
+            .sink { [unowned self] in
+                coordinator.toRestore(using: ndf, with: $0, from: self)
+            }.store(in: &cancellables)
 
         screenView.cancelButton
             .publisher(for: .touchUpInside)
@@ -68,18 +69,27 @@ public final class RestoreListController: UIViewController {
 
         screenView.driveButton
             .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapCloud(.drive, from: self) }
-            .store(in: &cancellables)
+            .sink { [unowned self] in
+                viewModel.didTapCloud(.drive, from: self)
+            }.store(in: &cancellables)
 
         screenView.icloudButton
             .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapCloud(.icloud, from: self) }
-            .store(in: &cancellables)
+            .sink { [unowned self] in
+                viewModel.didTapCloud(.icloud, from: self)
+            }.store(in: &cancellables)
 
         screenView.dropboxButton
             .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapCloud(.dropbox, from: self) }
-            .store(in: &cancellables)
+            .sink { [unowned self] in
+                viewModel.didTapCloud(.dropbox, from: self)
+            }.store(in: &cancellables)
+
+        screenView.sftpButton
+            .publisher(for: .touchUpInside)
+            .sink { [unowned self] in
+                viewModel.didTapCloud(.sftp, from: self)
+            }.store(in: &cancellables)
     }
 
     @objc private func didTapBack() {
diff --git a/Sources/RestoreFeature/Service/MockRestoreService.swift b/Sources/RestoreFeature/Service/MockRestoreService.swift
deleted file mode 100644
index 1a013434211580df1c99ac1fa3842ab0fc797d59..0000000000000000000000000000000000000000
--- a/Sources/RestoreFeature/Service/MockRestoreService.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-import UIKit
-import Models
-import Combine
-import Foundation
-import GoogleDriveFeature
-import DependencyInjection
-
-public struct RestoreServiceMock: RestoreServiceType {
-    public var inProgress: AnyPublisher<Void, Never> {
-        fatalError()
-    }
-
-    public var settings: AnyPublisher<RestoreSettings, Never> {
-        fatalError()
-    }
-
-    public init() {}
-
-    public func didSelectBackup(at url: URL) {}
-
-    public func authorize(service: CloudService, from: UIViewController) {}
-
-    public func download(
-        from settings: RestoreSettings,
-        progress: @escaping RestoreProgress,
-        whenFinished: @escaping RestoreDownloadFinished
-    ) {
-        fatalError()
-    }
-}
diff --git a/Sources/RestoreFeature/Service/RestoreServiceType.swift b/Sources/RestoreFeature/Service/RestoreServiceType.swift
deleted file mode 100644
index 78a32e9f6d9e199d78fa9c17a4a06b7ca1de51f3..0000000000000000000000000000000000000000
--- a/Sources/RestoreFeature/Service/RestoreServiceType.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-import UIKit
-import Models
-import Combine
-
-public typealias RestoreProgress = (Float) -> Void
-public typealias RestoreDownloadFinished = (Result<Data, Error>) -> Void
-
-public protocol RestoreServiceType {
-    var inProgress: AnyPublisher<Void, Never> { get }
-
-    var settings: AnyPublisher<RestoreSettings, Never> { get }
-
-    func authorize(service: CloudService, from: UIViewController)
-
-    func download(
-        from settings: RestoreSettings,
-        progress: @escaping RestoreProgress,
-        whenFinished: @escaping RestoreDownloadFinished
-    )
-}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index 3e91448aed07d65e2cdf650033be9d15be08fb4d..a4c2402e819c0576afdacc501c4550cd3656269f 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -6,20 +6,26 @@ import Combine
 import BackupFeature
 import DependencyInjection
 
+import SFTPFeature
 import iCloudFeature
 import DropboxFeature
 import GoogleDriveFeature
 
 final class RestoreListViewModel {
-    @Dependency private var icloud: iCloudInterface
-    @Dependency private var dropbox: DropboxInterface
-    @Dependency private var drive: GoogleDriveInterface
+    @Dependency private var sftpService: SFTPService
+    @Dependency private var icloudService: iCloudInterface
+    @Dependency private var dropboxService: DropboxInterface
+    @Dependency private var googleDriveService: GoogleDriveInterface
 
-    var hud: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() }
-    var didFetchBackup: AnyPublisher<RestoreSettings, Never> { backupSubject.eraseToAnyPublisher() }
+    var hudPublisher: AnyPublisher<HUDStatus, Never> {
+        hudSubject.eraseToAnyPublisher()
+    }
 
-    private var dropboxAuthCancellable: AnyCancellable?
+    var backupPublisher: AnyPublisher<RestoreSettings, Never> {
+        backupSubject.eraseToAnyPublisher()
+    }
 
+    private var dropboxAuthCancellable: AnyCancellable?
     private let hudSubject = PassthroughSubject<HUDStatus, Never>()
     private let backupSubject = PassthroughSubject<RestoreSettings, Never>()
 
@@ -31,15 +37,43 @@ final class RestoreListViewModel {
             didRequestICloudAuthorization()
         case .dropbox:
             didRequestDropboxAuthorization(from: parent)
+        case .sftp:
+            didRequestSFTPAuthorization(from: parent)
         }
     }
 
+    private func didRequestSFTPAuthorization(from controller: UIViewController) {
+        let params = SFTPAuthorizationParams(controller, { [weak self] in
+            guard let self = self else { return }
+            controller.navigationController?.popViewController(animated: true)
+
+            self.hudSubject.send(.on(nil))
+
+            self.sftpService.fetchMetadata{ result in
+                switch result {
+                case .success(let settings):
+                    self.hudSubject.send(.none)
+
+                    if let settings = settings {
+                        self.backupSubject.send(settings)
+                    } else {
+                        self.backupSubject.send(.init(cloudService: .sftp))
+                    }
+                case .failure(let error):
+                    self.hudSubject.send(.error(.init(with: error)))
+                }
+            }
+        })
+
+        sftpService.authorizeFlow(params)
+    }
+
     private func didRequestDriveAuthorization(from controller: UIViewController) {
-        drive.authorize(presenting: controller) { authResult in
+        googleDriveService.authorize(presenting: controller) { authResult in
             switch authResult {
             case .success:
                 self.hudSubject.send(.on(nil))
-                self.drive.downloadMetadata { downloadResult in
+                self.googleDriveService.downloadMetadata { downloadResult in
                     switch downloadResult {
                     case .success(let metadata):
                         var backup: Backup?
@@ -62,10 +96,10 @@ final class RestoreListViewModel {
     }
 
     private func didRequestICloudAuthorization() {
-        if icloud.isAuthorized() {
+        if icloudService.isAuthorized() {
             self.hudSubject.send(.on(nil))
 
-            icloud.downloadMetadata { result in
+            icloudService.downloadMetadata { result in
                 switch result {
                 case .success(let metadata):
                     var backup: Backup?
@@ -83,12 +117,12 @@ final class RestoreListViewModel {
         } else {
             /// This could be an alert controller asking if user wants to enable/deeplink
             ///
-            icloud.openSettings()
+            icloudService.openSettings()
         }
     }
 
     private func didRequestDropboxAuthorization(from controller: UIViewController) {
-        dropboxAuthCancellable = dropbox.authorize(presenting: controller)
+        dropboxAuthCancellable = dropboxService.authorize(presenting: controller)
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] authResult in
                 switch authResult {
@@ -96,7 +130,7 @@ final class RestoreListViewModel {
                     guard bool == true else { return }
 
                     self.hudSubject.send(.on(nil))
-                    dropbox.downloadMetadata { metadataResult in
+                    dropboxService.downloadMetadata { metadataResult in
                         switch metadataResult {
                         case .success(let metadata):
                             var backup: Backup?
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index 37a5bbe469665e83df59f9a5c3215c4223e426d5..bea37e5113ad33a547e5680e478a0ed7717db741 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -8,6 +8,7 @@ import Integration
 import BackupFeature
 import DependencyInjection
 
+import SFTPFeature
 import iCloudFeature
 import DropboxFeature
 import GoogleDriveFeature
@@ -38,13 +39,16 @@ extension RestorationStep: Equatable {
 }
 
 final class RestoreViewModel {
+    @Dependency private var sftpService: SFTPService
     @Dependency private var iCloudService: iCloudInterface
     @Dependency private var dropboxService: DropboxInterface
     @Dependency private var googleService: GoogleDriveInterface
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
-    var step: AnyPublisher<RestorationStep, Never> { stepRelay.eraseToAnyPublisher() }
+    var step: AnyPublisher<RestorationStep, Never> {
+        stepRelay.eraseToAnyPublisher()
+    }
 
     // TO REFACTOR:
     //
@@ -80,6 +84,22 @@ final class RestoreViewModel {
             downloadBackupForDropbox(backup)
         case .icloud:
             downloadBackupForiCloud(backup)
+        case .sftp:
+            downloadBackupForSFTP(backup)
+        }
+    }
+
+    private func downloadBackupForSFTP(_ backup: Backup) {
+        sftpService.downloadBackup(path: backup.id) { [weak self] in
+            guard let self = self else { return }
+            self.stepRelay.send(.downloading(backup.size, backup.size))
+
+            switch $0 {
+            case .success(let data):
+                self.continueRestoring(data: data)
+            case .failure(let error):
+                self.stepRelay.send(.failDownload(error))
+            }
         }
     }
 
diff --git a/Sources/RestoreFeature/Views/RestoreListView.swift b/Sources/RestoreFeature/Views/RestoreListView.swift
index 2a688760bc68cd509775fa97d28a37538dc44d3a..a955173a742b7c0b94f95601f90634353ada327f 100644
--- a/Sources/RestoreFeature/Views/RestoreListView.swift
+++ b/Sources/RestoreFeature/Views/RestoreListView.swift
@@ -6,6 +6,7 @@ final class RestoreListView: UIView {
     let stackView = UIStackView()
     let firstSubtitleLabel = UILabel()
     let secondSubtitleLabel = UILabel()
+    let sftpButton = RowButton()
     let driveButton = RowButton()
     let icloudButton = RowButton()
     let dropboxButton = RowButton()
@@ -34,6 +35,7 @@ final class RestoreListView: UIView {
         secondSubtitleLabel.numberOfLines = 0
         secondSubtitleLabel.attributedText = attrString
 
+        sftpButton.setup(title: Localized.Backup.sftp, icon: Asset.restoreSFTP.image)
         icloudButton.setup(title: Localized.Backup.iCloud, icon: Asset.restoreIcloud.image)
         dropboxButton.setup(title: Localized.Backup.dropbox, icon: Asset.restoreDropbox.image)
         driveButton.setup(title: Localized.Backup.googleDrive, icon: Asset.restoreDrive.image)
@@ -41,9 +43,11 @@ final class RestoreListView: UIView {
         cancelButton.set(style: .seeThrough, title: Localized.AccountRestore.List.cancel)
 
         stackView.axis = .vertical
+        stackView.distribution = .fillEqually
         stackView.addArrangedSubview(driveButton)
         stackView.addArrangedSubview(icloudButton)
         stackView.addArrangedSubview(dropboxButton)
+        stackView.addArrangedSubview(sftpButton)
 
         addSubview(titleLabel)
         addSubview(firstSubtitleLabel)
@@ -51,35 +55,35 @@ final class RestoreListView: UIView {
         addSubview(stackView)
         addSubview(cancelButton)
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(15)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        firstSubtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        firstSubtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        secondSubtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(firstSubtitleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        secondSubtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(firstSubtitleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(secondSubtitleLabel.snp.bottom).offset(28)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(secondSubtitleLabel.snp.bottom).offset(28)
+            $0.left.equalToSuperview().offset(24)
+            $0.right.equalToSuperview().offset(-24)
         }
 
-        cancelButton.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
+        cancelButton.snp.makeConstraints {
+            $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(20)
+            $0.left.equalToSuperview().offset(40)
+            $0.right.equalToSuperview().offset(-40)
+            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
         }
     }
 
diff --git a/Sources/RestoreFeature/Views/RestoreView.swift b/Sources/RestoreFeature/Views/RestoreView.swift
index e36e5d5b1dd97cb735ac84f9d1c5c56fdb19b5c3..ba2e643cafc4dcca652ac7daac8ad14d22f52d15 100644
--- a/Sources/RestoreFeature/Views/RestoreView.swift
+++ b/Sources/RestoreFeature/Views/RestoreView.swift
@@ -39,36 +39,36 @@ final class RestoreView: UIView {
         bottomStackView.addArrangedSubview(cancelButton)
         bottomStackView.addArrangedSubview(backButton)
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(20)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalTo(safeAreaLayoutGuide).offset(20)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-38)
         }
 
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
+        subtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-38)
         }
 
-        detailsView.snp.makeConstraints { make in
-            make.top.equalTo(subtitleLabel.snp.bottom).offset(40)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
+        detailsView.snp.makeConstraints {
+            $0.top.equalTo(subtitleLabel.snp.bottom).offset(40)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
         }
 
-        progressView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(detailsView.snp.bottom)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
+        progressView.snp.makeConstraints {
+            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
         }
 
-        bottomStackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
+        bottomStackView.snp.makeConstraints {
+            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
+            $0.left.equalToSuperview().offset(40)
+            $0.right.equalToSuperview().offset(-40)
+            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
         }
     }
 
@@ -151,6 +151,8 @@ private extension CloudService {
             return Localized.Backup.iCloud
         case .dropbox:
             return Localized.Backup.dropbox
+        case .sftp:
+            return Localized.Backup.sftp
         }
     }
 
@@ -162,6 +164,8 @@ private extension CloudService {
             return Asset.restoreIcloud.image
         case .dropbox:
             return Asset.restoreDropbox.image
+        case .sftp:
+            return Asset.restoreSFTP.image
         }
     }
 }
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift b/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..389cdd46fc0c0136247e09f3dbd900ec265a7858
--- /dev/null
+++ b/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift
@@ -0,0 +1,54 @@
+import Shout
+import Socket
+import Keychain
+import Foundation
+import DependencyInjection
+
+public struct SFTPAuthenticator {
+    public var authenticate: (String, String, String) throws -> Void
+
+    public func callAsFunction(host: String, username: String, password: String) throws {
+        try authenticate(host, username, password)
+    }
+}
+
+extension SFTPAuthenticator {
+    static let mock = SFTPAuthenticator { host, username, password in
+        print("^^^ Requested authentication on sftp service.")
+        print("^^^ Host: \(host)")
+        print("^^^ Username: \(username)")
+        print("^^^ Password: \(password)")
+    }
+
+    static let live = SFTPAuthenticator { host, username, password in
+        do {
+            try SSH.connect(
+                host: host,
+                port: 22,
+                username: username,
+                authMethod: SSHPassword(password)) { ssh in
+                    _ = try ssh.openSftp()
+
+                    let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
+                    try keychain.store(key: .host, value: host)
+                    try keychain.store(key: .pwd, value: password)
+                    try keychain.store(key: .username, value: username)
+                }
+        } catch {
+            if let error = error as? SSHError {
+                print(error.kind)
+                print(error.message)
+                print(error.description)
+            } else if let error = error as? Socket.Error {
+                print(error.errorCode)
+                print(error.description)
+                print(error.errorReason)
+                print(error.localizedDescription)
+            } else {
+                print(error.localizedDescription)
+            }
+
+            throw error
+        }
+    }
+}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6a435df0051a2755f2fb73522f7be92a24c4dd77
--- /dev/null
+++ b/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift
@@ -0,0 +1,56 @@
+import Shout
+import Socket
+import Keychain
+import Foundation
+import DependencyInjection
+
+public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
+
+public struct SFTPDownloader {
+    public var download: (String, @escaping SFTPDownloadResult) -> Void
+
+    public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) {
+        download(path, completion)
+    }
+}
+
+extension SFTPDownloader {
+    static let mock = SFTPDownloader { path, _ in
+        print("^^^ Requested backup download on sftp service.")
+        print("^^^ Path: \(path)")
+    }
+
+    static let live = SFTPDownloader { path, completion in
+        DispatchQueue.global().async {
+            do {
+                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
+                let host = try keychain.get(key: .host)
+                let password = try keychain.get(key: .pwd)
+                let username = try keychain.get(key: .username)
+
+                let ssh = try SSH(host: host!, port: 22)
+                try ssh.authenticate(username: username!, password: password!)
+                let sftp = try ssh.openSftp()
+
+                let localURL = FileManager.default
+                    .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+                    .appendingPathComponent("sftp")
+
+                try sftp.download(remotePath: path, localURL: localURL)
+
+                let data = try Data(contentsOf: localURL)
+                completion(.success(data))
+            } catch {
+                completion(.failure(error))
+
+                if var error = error as? SSHError {
+                    print(error.kind)
+                    print(error.message)
+                    print(error.description)
+                } else {
+                    print(error.localizedDescription)
+                }
+            }
+        }
+    }
+}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift b/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a27df80ffe8e9be6f41f09185e9962351c44cff9
--- /dev/null
+++ b/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift
@@ -0,0 +1,68 @@
+import Shout
+import Socket
+import Models
+import Keychain
+import Foundation
+import DependencyInjection
+
+public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
+
+public struct SFTPFetcher {
+    public var fetch: (@escaping SFTPFetchResult) -> Void
+
+    public func callAsFunction(completion: @escaping SFTPFetchResult) {
+        fetch(completion)
+    }
+}
+
+extension SFTPFetcher {
+    static let mock = SFTPFetcher { _ in
+        print("^^^ Requested backup metadata on sftp service.")
+    }
+
+    static let live = SFTPFetcher { completion in
+        DispatchQueue.global().async {
+            do {
+                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
+                let host = try keychain.get(key: .host)
+                let password = try keychain.get(key: .pwd)
+                let username = try keychain.get(key: .username)
+
+                let ssh = try SSH(host: host!, port: 22)
+                try ssh.authenticate(username: username!, password: password!)
+                let sftp = try ssh.openSftp()
+
+                if let files = try? sftp.listFiles(in: "backup"),
+                   let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
+                    completion(.success(.init(
+                        backup: .init(
+                            id: "backup/backup.xxm",
+                            date: backup.value.lastModified,
+                            size: Float(backup.value.size)
+                        ),
+                        cloudService: .sftp
+                    )))
+
+                    return
+                }
+
+                completion(.success(nil))
+            } catch {
+                if let error = error as? SSHError {
+                    print(error.kind)
+                    print(error.message)
+                    print(error.description)
+                } else if let error = error as? Socket.Error {
+                    print(error.errorCode)
+                    print(error.description)
+                    print(error.errorReason)
+                    print(error.localizedDescription)
+                } else {
+                    print(error.localizedDescription)
+                }
+
+                completion(.failure(error))
+            }
+        }
+    }
+}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift
new file mode 100644
index 0000000000000000000000000000000000000000..fee691d1b7e1669226fecfad6ecc37c97e64f9c5
--- /dev/null
+++ b/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift
@@ -0,0 +1,69 @@
+import Shout
+import Socket
+import Models
+import Keychain
+import Foundation
+import DependencyInjection
+
+public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
+
+public struct SFTPUploader {
+    public var upload: (URL, @escaping SFTPUploadResult) -> Void
+
+    public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) {
+        upload(url, completion)
+    }
+}
+
+extension SFTPUploader {
+    static let mock = SFTPUploader(
+        upload: { url, _ in
+            print("^^^ Requested upload on sftp service")
+            print("^^^ URL path: \(url.path)")
+        }
+    )
+
+    static let live = SFTPUploader { url, completion in
+        DispatchQueue.global().async {
+            do {
+                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
+                let host = try keychain.get(key: .host)
+                let password = try keychain.get(key: .pwd)
+                let username = try keychain.get(key: .username)
+
+                let ssh = try SSH(host: host!, port: 22)
+                try ssh.authenticate(username: username!, password: password!)
+                let sftp = try ssh.openSftp()
+
+                let data = try Data(contentsOf: url)
+
+                if (try? sftp.listFiles(in: "backup")) == nil {
+                    try sftp.createDirectory("backup")
+                }
+
+                try sftp.upload(data: data, remotePath: "backup/backup.xxm")
+
+                completion(.success(.init(
+                    id: "backup/backup.xxm",
+                    date: Date(),
+                    size: Float(data.count)
+                )))
+            } catch {
+                if let error = error as? SSHError {
+                    print(error.kind)
+                    print(error.message)
+                    print(error.description)
+                } else if let error = error as? Socket.Error {
+                    print(error.errorCode)
+                    print(error.description)
+                    print(error.errorReason)
+                    print(error.localizedDescription)
+                } else {
+                    print(error.localizedDescription)
+                }
+
+                completion(.failure(error))
+            }
+        }
+    }
+}
diff --git a/Sources/SFTPFeature/SFTPController.swift b/Sources/SFTPFeature/SFTPController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0a8c8d38ed8dbd9d802d58962b468b11f4c57431
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPController.swift
@@ -0,0 +1,96 @@
+import HUD
+import UIKit
+import Combine
+import DependencyInjection
+import ScrollViewController
+
+public final class SFTPController: UIViewController {
+    @Dependency private var hud: HUDType
+
+    lazy private var screenView = SFTPView()
+    lazy private var scrollViewController = ScrollViewController()
+
+    private let completion: () -> Void
+    private let viewModel = SFTPViewModel()
+    private var cancellables = Set<AnyCancellable>()
+
+    public init(_ completion: @escaping () -> Void) {
+        self.completion = completion
+        super.init(nibName: nil, bundle: nil)
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    public override func viewDidLoad() {
+        super.viewDidLoad()
+        setupScrollView()
+        setupNavigationBar()
+        setupBindings()
+    }
+
+    private func setupScrollView() {
+        scrollViewController.scrollView.backgroundColor = .white
+
+        addChild(scrollViewController)
+        view.addSubview(scrollViewController.view)
+        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+        scrollViewController.didMove(toParent: self)
+        scrollViewController.contentView = screenView
+    }
+
+    private func setupNavigationBar() {
+        navigationItem.backButtonTitle = ""
+
+        let back = UIButton.back()
+        back.addTarget(self, action: #selector(didTapBack), for: .touchUpInside)
+
+        navigationItem.leftBarButtonItem = UIBarButtonItem(
+            customView: UIStackView(arrangedSubviews: [back])
+        )
+    }
+
+    private func setupBindings() {
+        viewModel.hudPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [hud] in hud.update(with: $0) }
+            .store(in: &cancellables)
+
+        viewModel.authPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in completion() }
+            .store(in: &cancellables)
+
+        screenView.hostField
+            .textPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in viewModel.didEnterHost($0) }
+            .store(in: &cancellables)
+
+        screenView.usernameField
+            .textPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in viewModel.didEnterUsername($0) }
+            .store(in: &cancellables)
+
+        screenView.passwordField
+            .textPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in viewModel.didEnterPassword($0) }
+            .store(in: &cancellables)
+
+        viewModel.statePublisher
+            .receive(on: DispatchQueue.main)
+            .map(\.isButtonEnabled)
+            .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
+            .store(in: &cancellables)
+
+        screenView.loginButton
+            .publisher(for: .touchUpInside)
+            .sink { [unowned self] in viewModel.didTapLogin() }
+            .store(in: &cancellables)
+    }
+
+    @objc private func didTapBack() {
+        navigationController?.popViewController(animated: true)
+    }
+}
diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f1a908564df810ed2aeaa1b4af358b367253f84b
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPService.swift
@@ -0,0 +1,47 @@
+import UIKit
+import Keychain
+import Presentation
+import DependencyInjection
+
+public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
+
+public struct SFTPService {
+    public var isAuthorized: () -> Bool
+    public var fetchMetadata: SFTPFetcher
+    public var uploadBackup: SFTPUploader
+    public var authorizeFlow: (SFTPAuthorizationParams) -> Void
+    public var authenticate: SFTPAuthenticator
+    public var downloadBackup: SFTPDownloader
+}
+
+public extension SFTPService {
+    static var mock = SFTPService(
+        isAuthorized: { true },
+        fetchMetadata: .mock,
+        uploadBackup: .mock,
+        authorizeFlow: { (_, completion) in completion() },
+        authenticate: .mock,
+        downloadBackup: .mock
+    )
+
+    static var live = SFTPService(
+        isAuthorized: {
+            if let keychain = try? DependencyInjection.Container.shared.resolve() as KeychainHandling,
+               let pwd = try? keychain.get(key: .pwd),
+               let host = try? keychain.get(key: .host),
+               let username = try? keychain.get(key: .username) {
+                return true
+            }
+
+            return false
+        },
+        fetchMetadata: .live,
+        uploadBackup: .live ,
+        authorizeFlow: { controller, completion in
+            var pushPresenter: Presenting = PushPresenter()
+            pushPresenter.present(SFTPController(completion), from: controller)
+        },
+        authenticate: .live,
+        downloadBackup: .live
+    )
+}
diff --git a/Sources/SFTPFeature/SFTPView.swift b/Sources/SFTPFeature/SFTPView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3e62e4caeb0eca188fd1dad4db740545f0b3f045
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPView.swift
@@ -0,0 +1,76 @@
+import UIKit
+import Shared
+import InputField
+
+final class SFTPView: UIView {
+    let titleLabel = UILabel()
+    let subtitleLabel = UILabel()
+    let hostField = OutlinedInputField()
+    let usernameField = OutlinedInputField()
+    let passwordField = OutlinedInputField()
+    let loginButton = CapsuleButton()
+    let stackView = UIStackView()
+
+    init() {
+        super.init(frame: .zero)
+        backgroundColor = Asset.neutralWhite.color
+
+        titleLabel.textColor = Asset.neutralDark.color
+        titleLabel.text = Localized.AccountRestore.Sftp.title
+        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+        let paragraph = NSMutableParagraphStyle()
+        paragraph.alignment = .left
+        paragraph.lineHeightMultiple = 1.15
+
+        let attString = NSAttributedString(
+            string: Localized.AccountRestore.Sftp.subtitle,
+            attributes: [
+                .foregroundColor: Asset.neutralBody.color,
+                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+                .paragraphStyle: paragraph
+            ])
+
+        subtitleLabel.numberOfLines = 0
+        subtitleLabel.attributedText = attString
+
+        hostField.setup(title: Localized.AccountRestore.Sftp.host)
+        usernameField.setup(title: Localized.AccountRestore.Sftp.username)
+        passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
+
+        loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
+
+        stackView.spacing = 30
+        stackView.axis = .vertical
+        stackView.distribution = .fillEqually
+        stackView.addArrangedSubview(hostField)
+        stackView.addArrangedSubview(usernameField)
+        stackView.addArrangedSubview(passwordField)
+        stackView.addArrangedSubview(loginButton)
+
+        addSubview(titleLabel)
+        addSubview(subtitleLabel)
+        addSubview(stackView)
+
+        titleLabel.snp.makeConstraints {
+            $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
+        }
+
+        subtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
+        }
+
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-38)
+            $0.bottom.lessThanOrEqualToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/SFTPFeature/SFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dcf397a1da1c474681001b87df0d148ae26b2706
--- /dev/null
+++ b/Sources/SFTPFeature/SFTPViewModel.swift
@@ -0,0 +1,77 @@
+import HUD
+import Combine
+import Foundation
+import DependencyInjection
+
+struct SFTPViewState {
+    var host: String = ""
+    var username: String = ""
+    var password: String = ""
+    var isButtonEnabled: Bool = false
+}
+
+final class SFTPViewModel {
+    @Dependency private var service: SFTPService
+
+    var hudPublisher: AnyPublisher<HUDStatus, Never> {
+        hudSubject.eraseToAnyPublisher()
+    }
+
+    var statePublisher: AnyPublisher<SFTPViewState, Never> {
+        stateSubject.eraseToAnyPublisher()
+    }
+
+    var authPublisher: AnyPublisher<Void, Never> {
+        authSubject.eraseToAnyPublisher()
+    }
+
+    private let authSubject = PassthroughSubject<Void, Never>()
+    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
+    private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
+
+    func didEnterHost(_ string: String) {
+        stateSubject.value.host = string
+        validate()
+    }
+
+    func didEnterUsername(_ string: String) {
+        stateSubject.value.username = string
+        validate()
+    }
+
+    func didEnterPassword(_ string: String) {
+        stateSubject.value.password = string
+        validate()
+    }
+
+    func didTapLogin() {
+        hudSubject.send(.on(nil))
+
+        let host = stateSubject.value.host
+        let username = stateSubject.value.username
+        let password = stateSubject.value.password
+
+        DispatchQueue.global().async { [weak self] in
+            guard let self = self else { return }
+            do {
+                try self.service.authenticate(
+                    host: host,
+                    username: username,
+                    password: password
+                )
+
+                self.hudSubject.send(.none)
+                self.authSubject.send(())
+            } catch {
+                self.hudSubject.send(.error(.init(with: error)))
+            }
+        }
+    }
+
+    private func validate() {
+        stateSubject.value.isButtonEnabled =
+        !stateSubject.value.host.isEmpty &&
+        !stateSubject.value.username.isEmpty &&
+        !stateSubject.value.password.isEmpty
+    }
+}
diff --git a/Sources/Shared/AutoGenerated/Assets.swift b/Sources/Shared/AutoGenerated/Assets.swift
index 0f3b9d9280e9df670205e4f58eb7adc194456358..6755de526a369e8245365d38a47984ea96a02e42 100644
--- a/Sources/Shared/AutoGenerated/Assets.swift
+++ b/Sources/Shared/AutoGenerated/Assets.swift
@@ -97,6 +97,7 @@ public enum Asset {
   public static let requestsTabReceived = ImageAsset(name: "requests_tab_received")
   public static let requestsTabSent = ImageAsset(name: "requests_tab_sent")
   public static let requestsVerificationFailed = ImageAsset(name: "requests_verification_failed")
+  public static let restoreSFTP = ImageAsset(name: "restore_SFTP")
   public static let restoreDrive = ImageAsset(name: "restore_drive")
   public static let restoreDropbox = ImageAsset(name: "restore_dropbox")
   public static let restoreIcloud = ImageAsset(name: "restore_icloud")
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift
index a7b424f76629a0d52e217855b372e1a332da30c3..9f9fbfb27622937b8f189ffb9327c60ea9a681d0 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/Shared/AutoGenerated/Strings.swift
@@ -211,6 +211,20 @@ public enum Localized {
       /// Backup not found
       public static let title = Localized.tr("Localizable", "accountRestore.notFound.title")
     }
+    public enum Sftp {
+      /// Host
+      public static let host = Localized.tr("Localizable", "accountRestore.sftp.host")
+      /// Login
+      public static let login = Localized.tr("Localizable", "accountRestore.sftp.login")
+      /// Password
+      public static let password = Localized.tr("Localizable", "accountRestore.sftp.password")
+      /// Login to your server. Your credentials will be automatically and securley saved locally on your device.
+      public static let subtitle = Localized.tr("Localizable", "accountRestore.sftp.subtitle")
+      /// Login to your SFTP
+      public static let title = Localized.tr("Localizable", "accountRestore.sftp.title")
+      /// Username
+      public static let username = Localized.tr("Localizable", "accountRestore.sftp.username")
+    }
     public enum Success {
       /// You now have access to all your contacts.
       public static let subtitle = Localized.tr("Localizable", "accountRestore.success.subtitle")
@@ -236,6 +250,8 @@ public enum Localized {
     public static let header = Localized.tr("Localizable", "backup.header")
     /// iCloud
     public static let iCloud = Localized.tr("Localizable", "backup.iCloud")
+    /// SFTP
+    public static let sftp = Localized.tr("Localizable", "backup.SFTP")
     /// Back up your account to a cloud storage service, you can restore it along with only your contacts when you reinstall xx Messenger on another device.
     public static let subtitle = Localized.tr("Localizable", "backup.subtitle")
     public enum Config {
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json b/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json
new file mode 100644
index 0000000000000000000000000000000000000000..5c2f805eb3317d819659b5d73f14b6a1b249804f
--- /dev/null
+++ b/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+  "images" : [
+    {
+      "filename" : "Icon.pdf",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf b/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..08fcf11943d754dfc4e7427d290def530dc7dbcb
--- /dev/null
+++ b/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf
@@ -0,0 +1,447 @@
+%PDF-1.7
+
+1 0 obj
+  << >>
+endobj
+
+2 0 obj
+  << /Length 3 0 R >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 15.426392 33.381592 cm
+0.693500 0.711750 0.730000 scn
+23.717567 7.403564 m
+1.429677 7.403564 l
+0.639939 7.403564 0.000000 6.763626 0.000000 5.973887 c
+0.000000 0.000000 l
+25.147245 0.000000 l
+25.147245 5.973887 l
+25.147245 6.763626 24.507305 7.403564 23.717567 7.403564 c
+h
+3.436188 1.708138 m
+2.466323 1.708138 l
+2.293242 1.708138 2.152940 1.848440 2.152940 2.021521 c
+2.152940 2.194602 2.293242 2.334904 2.466323 2.334904 c
+3.436188 2.334904 l
+3.609268 2.334904 3.749571 2.194602 3.749571 2.021521 c
+3.749265 1.848440 3.608962 1.708138 3.436188 1.708138 c
+h
+3.436188 3.388399 m
+2.466323 3.388399 l
+2.293242 3.388399 2.152940 3.528702 2.152940 3.701782 c
+2.152940 3.874863 2.293242 4.015165 2.466323 4.015165 c
+3.436188 4.015165 l
+3.609268 4.015165 3.749571 3.874863 3.749571 3.701782 c
+3.749265 3.528702 3.608962 3.388399 3.436188 3.388399 c
+h
+3.436188 5.069273 m
+2.466323 5.069273 l
+2.293242 5.069273 2.152940 5.209575 2.152940 5.382656 c
+2.152940 5.555737 2.293242 5.696039 2.466323 5.696039 c
+3.436188 5.696039 l
+3.609268 5.696039 3.749571 5.555737 3.749571 5.382656 c
+3.749571 5.209575 3.608962 5.069273 3.436188 5.069273 c
+h
+6.515186 1.708138 m
+5.545321 1.708138 l
+5.372241 1.708138 5.231938 1.848440 5.231938 2.021521 c
+5.231938 2.194602 5.372241 2.334904 5.545321 2.334904 c
+6.515186 2.334904 l
+6.688267 2.334904 6.828569 2.194602 6.828569 2.021521 c
+6.828263 1.848440 6.687960 1.708138 6.515186 1.708138 c
+h
+6.515186 3.388399 m
+5.545321 3.388399 l
+5.372241 3.388399 5.231938 3.528702 5.231938 3.701782 c
+5.231938 3.874863 5.372241 4.015165 5.545321 4.015165 c
+6.515186 4.015165 l
+6.688267 4.015165 6.828569 3.874863 6.828569 3.701782 c
+6.828263 3.528702 6.687960 3.388399 6.515186 3.388399 c
+h
+6.515186 5.069273 m
+5.545321 5.069273 l
+5.372241 5.069273 5.231938 5.209575 5.231938 5.382656 c
+5.231938 5.555737 5.372241 5.696039 5.545321 5.696039 c
+6.515186 5.696039 l
+6.688267 5.696039 6.828569 5.555737 6.828569 5.382656 c
+6.828569 5.209575 6.687960 5.069273 6.515186 5.069273 c
+h
+9.594184 1.708138 m
+8.624320 1.708138 l
+8.451240 1.708138 8.310936 1.848440 8.310936 2.021521 c
+8.310936 2.194602 8.451240 2.334904 8.624320 2.334904 c
+9.594184 2.334904 l
+9.767264 2.334904 9.907569 2.194602 9.907569 2.021521 c
+9.907262 1.848440 9.766958 1.708138 9.594184 1.708138 c
+h
+9.594184 3.388399 m
+8.624320 3.388399 l
+8.451240 3.388399 8.310936 3.528702 8.310936 3.701782 c
+8.310936 3.874863 8.451240 4.015165 8.624320 4.015165 c
+9.594184 4.015165 l
+9.767264 4.015165 9.907569 3.874863 9.907569 3.701782 c
+9.907262 3.528702 9.766958 3.388399 9.594184 3.388399 c
+h
+9.594184 5.069273 m
+8.624320 5.069273 l
+8.451240 5.069273 8.310936 5.209575 8.310936 5.382656 c
+8.310936 5.555737 8.451240 5.696039 8.624320 5.696039 c
+9.594184 5.696039 l
+9.767264 5.696039 9.907569 5.555737 9.907569 5.382656 c
+9.907569 5.209575 9.766958 5.069273 9.594184 5.069273 c
+h
+12.673183 1.708138 m
+11.703625 1.708138 l
+11.530544 1.708138 11.390241 1.848440 11.390241 2.021521 c
+11.390241 2.194602 11.530544 2.334904 11.703625 2.334904 c
+12.673183 2.334904 l
+12.846264 2.334904 12.986566 2.194602 12.986566 2.021521 c
+12.986259 1.848440 12.845958 1.708138 12.673183 1.708138 c
+h
+12.673183 3.388399 m
+11.703625 3.388399 l
+11.530544 3.388399 11.390241 3.528702 11.390241 3.701782 c
+11.390241 3.874863 11.530544 4.015165 11.703625 4.015165 c
+12.673183 4.015165 l
+12.846264 4.015165 12.986566 3.874863 12.986566 3.701782 c
+12.986259 3.528702 12.845958 3.388399 12.673183 3.388399 c
+h
+12.673183 5.069273 m
+11.703625 5.069273 l
+11.530544 5.069273 11.390241 5.209575 11.390241 5.382656 c
+11.390241 5.555737 11.530544 5.696039 11.703625 5.696039 c
+12.673183 5.696039 l
+12.846264 5.696039 12.986566 5.555737 12.986566 5.382656 c
+12.986566 5.209575 12.845958 5.069273 12.673183 5.069273 c
+h
+16.798935 2.525446 m
+16.149193 2.525446 15.622601 3.052040 15.622601 3.701782 c
+15.622601 4.351524 16.149193 4.878118 16.798935 4.878118 c
+17.448677 4.878118 17.975273 4.351524 17.975273 3.701782 c
+17.975273 3.052040 17.448677 2.525446 16.798935 2.525446 c
+h
+21.504585 2.525446 m
+20.854843 2.525446 20.328251 3.052040 20.328251 3.701782 c
+20.328251 4.351524 20.854843 4.878118 21.504585 4.878118 c
+22.154327 4.878118 22.680923 4.351524 22.680923 3.701782 c
+22.680923 3.052040 22.154327 2.525446 21.504585 2.525446 c
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 15.426392 23.298096 cm
+0.693500 0.711750 0.730000 scn
+0.000000 7.403564 m
+0.000000 0.000000 l
+25.147245 0.000000 l
+25.147245 7.403564 l
+0.000000 7.403564 l
+h
+3.436188 1.707832 m
+2.466323 1.707832 l
+2.293242 1.707832 2.152940 1.847827 2.152940 2.021214 c
+2.152940 2.194602 2.293242 2.334598 2.466323 2.334598 c
+3.436188 2.334598 l
+3.609268 2.334598 3.749571 2.194602 3.749571 2.021214 c
+3.749571 1.847827 3.608962 1.707832 3.436188 1.707832 c
+h
+3.436188 3.388399 m
+2.466323 3.388399 l
+2.293242 3.388399 2.152940 3.528702 2.152940 3.701782 c
+2.152940 3.874863 2.293242 4.015165 2.466323 4.015165 c
+3.436188 4.015165 l
+3.609268 4.015165 3.749571 3.874863 3.749571 3.701782 c
+3.749265 3.528702 3.608962 3.388399 3.436188 3.388399 c
+h
+3.436188 5.068967 m
+2.466323 5.068967 l
+2.293242 5.068967 2.152940 5.209269 2.152940 5.382350 c
+2.152940 5.555430 2.293242 5.695733 2.466323 5.695733 c
+3.436188 5.695733 l
+3.609268 5.695733 3.749571 5.555430 3.749571 5.382350 c
+3.749265 5.209269 3.608962 5.068967 3.436188 5.068967 c
+h
+6.515186 1.707832 m
+5.545321 1.707832 l
+5.372241 1.707832 5.231938 1.847827 5.231938 2.021214 c
+5.231938 2.194602 5.372241 2.334598 5.545321 2.334598 c
+6.515186 2.334598 l
+6.688267 2.334598 6.828569 2.194602 6.828569 2.021214 c
+6.828569 1.847827 6.687960 1.707832 6.515186 1.707832 c
+h
+6.515186 3.388399 m
+5.545321 3.388399 l
+5.372241 3.388399 5.231938 3.528702 5.231938 3.701782 c
+5.231938 3.874863 5.372241 4.015165 5.545321 4.015165 c
+6.515186 4.015165 l
+6.688267 4.015165 6.828569 3.874863 6.828569 3.701782 c
+6.828263 3.528702 6.687960 3.388399 6.515186 3.388399 c
+h
+6.515186 5.068967 m
+5.545321 5.068967 l
+5.372241 5.068967 5.231938 5.209269 5.231938 5.382350 c
+5.231938 5.555430 5.372241 5.695733 5.545321 5.695733 c
+6.515186 5.695733 l
+6.688267 5.695733 6.828569 5.555430 6.828569 5.382350 c
+6.828263 5.209269 6.687960 5.068967 6.515186 5.068967 c
+h
+9.594184 1.707832 m
+8.624320 1.707832 l
+8.451240 1.707832 8.310936 1.847827 8.310936 2.021214 c
+8.310936 2.194602 8.451240 2.334598 8.624320 2.334598 c
+9.594184 2.334598 l
+9.767264 2.334598 9.907569 2.194602 9.907569 2.021214 c
+9.907569 1.847827 9.766958 1.707832 9.594184 1.707832 c
+h
+9.594184 3.388399 m
+8.624320 3.388399 l
+8.451240 3.388399 8.310936 3.528702 8.310936 3.701782 c
+8.310936 3.874863 8.451240 4.015165 8.624320 4.015165 c
+9.594184 4.015165 l
+9.767264 4.015165 9.907569 3.874863 9.907569 3.701782 c
+9.907262 3.528702 9.766958 3.388399 9.594184 3.388399 c
+h
+9.594184 5.068967 m
+8.624320 5.068967 l
+8.451240 5.068967 8.310936 5.209269 8.310936 5.382350 c
+8.310936 5.555430 8.451240 5.695733 8.624320 5.695733 c
+9.594184 5.695733 l
+9.767264 5.695733 9.907569 5.555430 9.907569 5.382350 c
+9.907262 5.209269 9.766958 5.068967 9.594184 5.068967 c
+h
+12.673183 1.707832 m
+11.703625 1.707832 l
+11.530544 1.707832 11.390241 1.847827 11.390241 2.021214 c
+11.390241 2.194602 11.530544 2.334598 11.703625 2.334598 c
+12.673183 2.334598 l
+12.846264 2.334598 12.986566 2.194602 12.986566 2.021214 c
+12.986566 1.847827 12.845958 1.707832 12.673183 1.707832 c
+h
+12.673183 3.388399 m
+11.703625 3.388399 l
+11.530544 3.388399 11.390241 3.528702 11.390241 3.701782 c
+11.390241 3.874863 11.530544 4.015165 11.703625 4.015165 c
+12.673183 4.015165 l
+12.846264 4.015165 12.986566 3.874863 12.986566 3.701782 c
+12.986259 3.528702 12.845958 3.388399 12.673183 3.388399 c
+h
+12.673183 5.068967 m
+11.703625 5.068967 l
+11.530544 5.068967 11.390241 5.209269 11.390241 5.382350 c
+11.390241 5.555430 11.530544 5.695733 11.703625 5.695733 c
+12.673183 5.695733 l
+12.846264 5.695733 12.986566 5.555430 12.986566 5.382350 c
+12.986259 5.209269 12.845958 5.068967 12.673183 5.068967 c
+h
+16.798935 2.525446 m
+16.149193 2.525446 15.622601 3.052040 15.622601 3.701782 c
+15.622601 4.351524 16.149193 4.878119 16.798935 4.878119 c
+17.448677 4.878119 17.975273 4.351524 17.975273 3.701782 c
+17.975273 3.052040 17.448677 2.525446 16.798935 2.525446 c
+h
+21.504585 2.525446 m
+20.854843 2.525446 20.328251 3.052040 20.328251 3.701782 c
+20.328251 4.351524 20.854843 4.878119 21.504585 4.878119 c
+22.154327 4.878119 22.680923 4.351524 22.680923 3.701782 c
+22.680923 3.052040 22.154327 2.525446 21.504585 2.525446 c
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 15.426392 13.214844 cm
+0.693500 0.711750 0.730000 scn
+0.000000 7.403564 m
+0.000000 1.429677 l
+0.000000 0.639939 0.639939 0.000000 1.429677 0.000000 c
+23.717876 0.000000 l
+24.507307 0.000000 25.147552 0.639939 25.147552 1.429677 c
+25.147552 7.403564 l
+0.000000 7.403564 l
+h
+3.436188 1.707830 m
+2.466323 1.707830 l
+2.293243 1.707830 2.152940 1.847827 2.152940 2.021214 c
+2.152940 2.194295 2.293243 2.334599 2.466323 2.334599 c
+3.436188 2.334599 l
+3.609269 2.334599 3.749571 2.194602 3.749571 2.021214 c
+3.749265 1.848134 3.608963 1.707830 3.436188 1.707830 c
+h
+3.436188 3.388398 m
+2.466323 3.388398 l
+2.293243 3.388398 2.152940 3.528395 2.152940 3.701782 c
+2.152940 3.875169 2.293243 4.015166 2.466323 4.015166 c
+3.436188 4.015166 l
+3.609269 4.015166 3.749571 3.875169 3.749571 3.701782 c
+3.749571 3.528395 3.608963 3.388398 3.436188 3.388398 c
+h
+3.436188 5.068966 m
+2.466323 5.068966 l
+2.293243 5.068966 2.152940 5.208962 2.152940 5.382350 c
+2.152940 5.555430 2.293243 5.695734 2.466323 5.695734 c
+3.436188 5.695734 l
+3.609269 5.695734 3.749571 5.555737 3.749571 5.382350 c
+3.749265 5.209269 3.608963 5.068966 3.436188 5.068966 c
+h
+6.515187 1.707830 m
+5.545322 1.707830 l
+5.372241 1.707830 5.231939 1.847827 5.231939 2.021214 c
+5.231939 2.194295 5.372241 2.334599 5.545322 2.334599 c
+6.515187 2.334599 l
+6.688267 2.334599 6.828570 2.194602 6.828570 2.021214 c
+6.828264 1.848134 6.687961 1.707830 6.515187 1.707830 c
+h
+6.515187 3.388398 m
+5.545322 3.388398 l
+5.372241 3.388398 5.231939 3.528395 5.231939 3.701782 c
+5.231939 3.875169 5.372241 4.015166 5.545322 4.015166 c
+6.515187 4.015166 l
+6.688267 4.015166 6.828570 3.875169 6.828570 3.701782 c
+6.828570 3.528395 6.687961 3.388398 6.515187 3.388398 c
+h
+6.515187 5.068966 m
+5.545322 5.068966 l
+5.372241 5.068966 5.231939 5.208962 5.231939 5.382350 c
+5.231939 5.555430 5.372241 5.695734 5.545322 5.695734 c
+6.515187 5.695734 l
+6.688267 5.695734 6.828570 5.555737 6.828570 5.382350 c
+6.828264 5.209269 6.687961 5.068966 6.515187 5.068966 c
+h
+9.594185 1.707830 m
+8.624321 1.707830 l
+8.451241 1.707830 8.310937 1.847827 8.310937 2.021214 c
+8.310937 2.194295 8.451241 2.334599 8.624321 2.334599 c
+9.594185 2.334599 l
+9.767265 2.334599 9.907570 2.194602 9.907570 2.021214 c
+9.907263 1.848134 9.766959 1.707830 9.594185 1.707830 c
+h
+9.594185 3.388398 m
+8.624321 3.388398 l
+8.451241 3.388398 8.310937 3.528395 8.310937 3.701782 c
+8.310937 3.875169 8.451241 4.015166 8.624321 4.015166 c
+9.594185 4.015166 l
+9.767265 4.015166 9.907570 3.875169 9.907570 3.701782 c
+9.907570 3.528395 9.766959 3.388398 9.594185 3.388398 c
+h
+9.594185 5.068966 m
+8.624321 5.068966 l
+8.451241 5.068966 8.310937 5.208962 8.310937 5.382350 c
+8.310937 5.555430 8.451241 5.695734 8.624321 5.695734 c
+9.594185 5.695734 l
+9.767265 5.695734 9.907570 5.555737 9.907570 5.382350 c
+9.907263 5.209269 9.766959 5.068966 9.594185 5.068966 c
+h
+12.673184 1.707830 m
+11.703626 1.707830 l
+11.530545 1.707830 11.390242 1.847827 11.390242 2.021214 c
+11.390242 2.194295 11.530545 2.334599 11.703626 2.334599 c
+12.673184 2.334599 l
+12.846266 2.334599 12.986567 2.194602 12.986567 2.021214 c
+12.986260 1.848134 12.845959 1.707830 12.673184 1.707830 c
+h
+12.673184 3.388398 m
+11.703626 3.388398 l
+11.530545 3.388398 11.390242 3.528395 11.390242 3.701782 c
+11.390242 3.875169 11.530545 4.015166 11.703626 4.015166 c
+12.673184 4.015166 l
+12.846266 4.015166 12.986567 3.875169 12.986567 3.701782 c
+12.986567 3.528395 12.845959 3.388398 12.673184 3.388398 c
+h
+12.673184 5.068966 m
+11.703626 5.068966 l
+11.530545 5.068966 11.390242 5.208962 11.390242 5.382350 c
+11.390242 5.555430 11.530545 5.695734 11.703626 5.695734 c
+12.673184 5.695734 l
+12.846266 5.695734 12.986567 5.555737 12.986567 5.382350 c
+12.986260 5.209269 12.845959 5.068966 12.673184 5.068966 c
+h
+16.798937 2.525447 m
+16.149195 2.525447 15.622602 3.052040 15.622602 3.701782 c
+15.622602 4.351524 16.149195 4.878119 16.798937 4.878119 c
+17.448679 4.878119 17.975275 4.351524 17.975275 3.701782 c
+17.975275 3.052040 17.448679 2.525447 16.798937 2.525447 c
+h
+21.504587 2.525447 m
+20.854845 2.525447 20.328253 3.052040 20.328253 3.701782 c
+20.328253 4.351524 20.854845 4.878119 21.504587 4.878119 c
+22.154329 4.878119 22.680925 4.351524 22.680925 3.701782 c
+22.680925 3.052040 22.154329 2.525447 21.504587 2.525447 c
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 17.400757 21.498291 cm
+0.693500 0.711750 0.730000 scn
+21.201620 0.918945 m
+0.000000 0.918945 l
+0.000000 -0.000067 l
+21.201620 -0.000067 l
+21.201620 0.918945 l
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 17.400757 31.582764 cm
+0.693500 0.711750 0.730000 scn
+21.201620 0.918945 m
+0.000000 0.918945 l
+0.000000 -0.000067 l
+21.201620 -0.000067 l
+21.201620 0.918945 l
+h
+f
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+  13265
+endobj
+
+4 0 obj
+  << /Annots []
+     /Type /Page
+     /MediaBox [ 0.000000 0.000000 56.000000 56.000000 ]
+     /Resources 1 0 R
+     /Contents 2 0 R
+     /Parent 5 0 R
+  >>
+endobj
+
+5 0 obj
+  << /Kids [ 4 0 R ]
+     /Count 1
+     /Type /Pages
+  >>
+endobj
+
+6 0 obj
+  << /Pages 5 0 R
+     /Type /Catalog
+  >>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000010 00000 n
+0000000034 00000 n
+0000013355 00000 n
+0000013379 00000 n
+0000013552 00000 n
+0000013626 00000 n
+trailer
+<< /ID [ (some) (id) ]
+   /Root 6 0 R
+   /Size 7
+>>
+startxref
+13685
+%%EOF
\ No newline at end of file
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 7c8da3fd206e97854424e16b94dbbc91a22b9d5f..3aa07cedf9576e85634d4d61ec701efcee5229c5 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -629,6 +629,8 @@
 = "Dropbox";
 "backup.googleDrive"
 = "Google Drive";
+"backup.SFTP"
+= "SFTP";
 
 // Settings - Delete Account
 
@@ -838,6 +840,19 @@
 "accountRestore.list.cancel"
 = "Cancel";
 
+"accountRestore.sftp.title"
+= "Login to your SFTP";
+"accountRestore.sftp.subtitle"
+= "Login to your server. Your credentials will be automatically and securely saved locally on your device.";
+"accountRestore.sftp.host"
+= "Host";
+"accountRestore.sftp.username"
+= "Username";
+"accountRestore.sftp.password"
+= "Password";
+"accountRestore.sftp.login"
+= "Login";
+
 "accountRestore.header"
 = "Account restore";
 "accountRestore.found.title"
diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
index eeb145af40bba398a5526019333b5de39606557e..102be628eff74bf62db32030a74c7eed314ba3f3 100644
--- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -27,6 +27,15 @@
         "version" : "1.4.0"
       }
     },
+    {
+      "identity" : "bluesocket",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/IBM-Swift/BlueSocket.git",
+      "state" : {
+        "revision" : "c9894fd117457f1d006575fbfb2fdfd6f79eac03",
+        "version" : "1.0.200"
+      }
+    },
     {
       "identity" : "boringssl-swiftpm",
       "kind" : "remoteSourceControl",
@@ -207,6 +216,15 @@
         "version" : "1.22.2"
       }
     },
+    {
+      "identity" : "libssh2prebuild",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/DimaRU/Libssh2Prebuild.git",
+      "state" : {
+        "branch" : "1.10.0+OpenSSL_1_1_1o",
+        "revision" : "a91bcf205a6cbc84144f840c44145656abbd266a"
+      }
+    },
     {
       "identity" : "nanopb",
       "kind" : "remoteSourceControl",
@@ -261,6 +279,14 @@
         "version" : "1.2.0"
       }
     },
+    {
+      "identity" : "shout",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/darrarski/Shout.git",
+      "state" : {
+        "revision" : "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"
+      }
+    },
     {
       "identity" : "snapkit",
       "kind" : "remoteSourceControl",