I recently built a private photo backup and synchronization setup using
Immich and Syncthing. The goal was to replace a cloud photo service
with something self-hosted, private, and accessible only from trusted networks.
The setup combines automatic phone photo synchronization, a web photo gallery,
private DNS, HTTPS certificates, and a reverse proxy in front of the web panels.
No real domains, private addresses, or internal paths are included here - only the general technical design.
Architecture
The system is split into clear parts:
- Syncthing synchronizes files between the phone and the server.
- Immich indexes and displays the photo library.
- Nginx provides HTTPS reverse proxy access.
- Local DNS resolves private service names inside the trusted network.
- Fail2ban protects the server while ignoring trusted local devices.
Phone
|
| Syncthing sync
v
Self-hosted server
|
| Synced photo folder
v
Immich external library
|
| HTTPS through Nginx
v
Private photo web interface
Private service access
The web panels are available through private hostnames. They are not public service endpoints.
They are resolved by local DNS and are meant to work only from LAN or a private VPN.
<private-photo-panel>
<private-sync-panel>
A simplified private DNS layout looks like this:
<private-photo-panel> -> <local-server>
<private-sync-panel> -> <local-server>
This keeps the setup convenient without exposing the panels directly to the public internet.
Syncthing folder design
Syncthing handles the phone-to-server photo transfer. For photos, the safer mode is:
Phone: Send Only
Server: Receive Only
This prevents the server from acting like a two-way editor of the phone gallery.
The phone sends photos, and the server receives them as a backup target.
Before syncing the real photo directory, I tested the connection with a small test folder.
After confirming file creation, deletion, and synchronization worked correctly, I moved the real photo folder into the setup.
Immich external library
The synchronized photo folder is mounted into the Immich container as a read-only external library.
/mnt/phone-photos
The read-only mount is intentional. Immich can scan, index, generate thumbnails,
and display the media, but it cannot modify or delete the original synchronized files.
Nginx reverse proxy
Nginx sits in front of both panels:
<private-photo-panel> -> Nginx -> Immich
<private-sync-panel> -> Nginx -> Syncthing GUI
The reverse proxy provides:
- HTTPS access
- clean private service names
- local-only access rules
- separation between browser access and backend containers
The services remain private, but they still use normal HTTPS certificates.
SSL certificates
Certificates are issued using DNS validation. This makes it possible to use valid HTTPS certificates
without exposing the web panels publicly.
Valid HTTPS certificate
Private DNS access
No direct public exposure of admin panels
Fail2ban adjustments
Fail2ban remains active for suspicious traffic and bot-like requests.
Trusted LAN and private VPN ranges are excluded from bans to prevent accidental lockouts during testing.
This keeps the protection active while avoiding bans of trusted devices.
Backup and working state
After the setup was confirmed working, I created a working-state backup containing:
- Nginx configuration
- DNS configuration
- Fail2ban configuration
- Immich Compose files
- container inspection reports
- deployment notes
- confirmation that Immich can see the synchronized photo folder
This gives me a safe restore point before making future changes.
Final result
Phone photo sync: working
Syncthing pairing: working
Immich external library: working
HTTPS access: working
LAN/VPN-only access: working
Working backup: created
This is now a practical private Google Photos replacement: photos are synchronized from the phone,
stored on my own server, indexed by Immich, and accessed through private endpoints.