How to Configure a Self-Hosted Zakirullin Files Sync Server with TLS
Zakirullin Files provides automatic TLS via Let's Encrypt using the Go autocert package, requiring only the CERT_DIR, API_URL, and APP_URL environment variables to enable HTTPS on ports 80 and 443.
Zakirullin Files is an open-source markdown-based file synchronization platform that ships with built-in TLS support. Configuring a self-hosted Zakirullin Files sync server with TLS requires understanding how the Go-based server handles certificate automation through environment variables and the autocert package. The implementation automatically manages Let's Encrypt certificates while providing HTTP-to-HTTPS redirection without manual certificate handling.
Built-in TLS Architecture
The server implements a dual-listener architecture that separates ACME challenge handling from secure API traffic. This design eliminates manual certificate management while ensuring encrypted connections by default.
Automatic Certificate Provisioning
The core TLS logic resides in server/sync/autocert.go, which creates an autocert.Manager configured with a HostPolicy restricting certificates to specific hostnames. The manager’s GetCertificate hook is wired into the TLS configuration in server/sync/webserver.go (lines 75-94), allowing the server to obtain and renew Let's Encrypt certificates on-the-fly. When ListenAndServeTLS("", "") is called, the manager supplies certificates dynamically rather than loading static key files from disk.
HTTP Challenge and Redirect Handling
Port 80 serves two purposes: handling the ACME "http-01" challenge and redirecting all other traffic to HTTPS. The certServer() function in server/sync/autocert.go spawns this plain-HTTP server, which uses autocertManager.HTTPHandler(nil) to intercept validation requests while sending all standard traffic to the HTTPS port. Port 443 exclusively serves the API and Progressive Web App (PWA) using the TLS configuration that delegates to the autocert manager.
Core Configuration Components
Environment Variables in server/config/config.go
The config.LoadBotConfig() function reads environment variables that control TLS behavior. Three variables are critical for certificate automation:
CERT_DIR(default:/tmp): Specifies the writable directory whereautocertcaches issued certificates, preventing rate-limit hits across restarts.API_URL: The full HTTPS URL (e.g.,https://api.files.md) for the API endpoint; the hostname is extracted viahostOf()and fed toautocert.HostWhitelist.APP_URL: The full HTTPS URL for the PWA frontend, also validated against the host whitelist.
Both URLs must include the https:// scheme and resolve to the server's public IP address.
Certificate Manager Setup in server/sync/autocert.go
This file defines the autocert.Manager with strict hostname validation. The HostWhitelist is populated from config.ServerCfg.APIHost() and config.ServerCfg.AppHost(), ensuring certificates are only requested for explicitly configured domains. The manager handles all ACME protocol negotiations with Let's Encrypt, including the HTTP-01 challenge responses served on port 80.
TLS Server Initialization in server/sync/webserver.go
When sync.Serve() detects a non-empty certDir, it constructs a tls.Config that references the manager’s GetCertificate method. The HTTPS server starts with ListenAndServeTLS("", ""), signaling Go to use the dynamic certificate provider. If certDir is empty, the server falls back to plain HTTP on port 8080 (lines 61-73), which is unsuitable for production but useful for local development.
Step-by-Step TLS Configuration
Follow these steps to deploy a production-ready TLS-enabled server:
-
Configure DNS: Create A records pointing your domains (e.g.,
api.example.comandapp.example.com) to your server's public IP address. -
Set Environment Variables: Create an
.envfile or export variables directly:API_URL=https://api.example.com APP_URL=https://app.example.com CERT_DIR=/opt/filesmd/certs LOG_FILE=/var/log/filesmd/server.log -
Open Firewall Ports: Allow inbound TCP traffic on ports 80 and 443. The server must be reachable from the internet for Let's Encrypt validation.
-
Deploy the Service: Run the initialization and deployment commands as documented in the repository. The first startup will trigger certificate issuance; subsequent restarts load cached certificates from
CERT_DIR. -
Verify Installation: Navigate to
https://api.example.comandhttps://app.example.com. The browser should display valid Let's Encrypt certificates without warnings.
Implementing Custom Certificates
For environments requiring corporate PKI certificates instead of Let's Encrypt, bypass autocert by providing a custom GetCertificate implementation:
tlsCfg := &tls.Config{
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair("/etc/ssl/certs/server.crt", "/etc/ssl/private/server.key")
if err != nil {
return nil, err
}
return &cert, nil
},
}
srv := &http.Server{
Addr: ":443",
TLSConfig: tlsCfg,
Handler: sync.Router(),
}
log.Fatal(srv.ListenAndServeTLS("", ""))
To use this approach, omit the CERT_DIR environment variable or set it to an empty string when calling sync.Serve(), forcing the server to skip automatic certificate management.
Summary
- Automatic TLS is provided via the
autocertGo package inserver/sync/autocert.go, handling Let's Encrypt issuance and renewal without manual intervention. - Port 80 handles ACME challenges and HTTP-to-HTTPS redirects, while port 443 serves encrypted API and PWA traffic using certificates supplied by the
autocert.Manager. - Configuration requires setting
CERT_DIR(writable path),API_URL, andAPP_URLinserver/config/config.gobefore callingsync.Serve(). - Certificate persistence relies on the
CERT_DIRcache; ensure this directory is backed up to avoid hitting Let's Encrypt rate limits during restarts. - Custom certificates are supported by implementing a custom
tls.Config.GetCertificatefunction and omitting theCERT_DIRparameter.
Frequently Asked Questions
What ports must be open for the TLS setup?
Ports 80 and 443 must be accessible from the internet. Port 80 handles the ACME HTTP-01 challenge required for Let's Encrypt validation and redirects all other traffic to HTTPS. Port 443 serves the actual encrypted API and PWA traffic. Firewall rules and NAT configurations must permit inbound TCP connections on both ports.
How does the server handle certificate persistence?
The autocert.Manager caches issued certificates in the directory specified by the CERT_DIR environment variable (defaulting to /tmp). This persistent storage allows the server to restart without re-requesting certificates from Let's Encrypt, preventing unnecessary load on ACME servers and avoiding rate limits. Ensure the process has write permissions to this directory.
Can I use custom certificates instead of Let's Encrypt?
Yes. To use corporate or self-signed certificates, omit the CERT_DIR environment variable and implement a custom GetCertificate function in your TLS configuration that loads PEM-encoded key pairs using tls.LoadX509KeyPair(). When certDir is empty, the server logic in server/sync/webserver.go defaults to HTTP on port 8080, so you must manually construct and start the HTTPS server with your custom tls.Config.
What happens if the certificate directory is not writable?
If the process lacks write permissions to CERT_DIR, the autocert.Manager cannot cache certificates, causing it to request new certificates on every restart. This rapidly exhausts the Let's Encrypt rate limits (currently 50 certificates per domain per week), resulting in failed TLS handshakes and service interruption. Always ensure CERT_DIR points to a writable, persistent volume.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →