Deployment & Signals¶
Running as a system service¶
Mini S3 is a single static binary (CGO_ENABLED=0), built for:
- Linux amd64 / arm64
- macOS arm64
- FreeBSD amd64 / arm64
Debian package¶
Each release is published as a .deb for amd64 and arm64 to the Forgejo
Debian package registry. The binary is static, so the single stable
distribution works on any Debian or Ubuntu release:
sudo curl -fsSL https://git.hanse.de/api/packages/stb/debian/repository.key \
-o /etc/apt/keyrings/forgejo-stb.asc
echo "deb [signed-by=/etc/apt/keyrings/forgejo-stb.asc] https://git.hanse.de/api/packages/stb/debian stable main" \
| sudo tee /etc/apt/sources.list.d/mini-s3.list
sudo apt update && sudo apt install mini-s3
The package installs:
/usr/bin/minis3/etc/minis3/minis3.toml, the example config, readable only by root and theminis3group (it holds bucket secrets), plus an empty/etc/minis3/conf.d/forgeneral.include- a
minis3system user, and theminis3.servicesystemd unit, which runs as that user withCAP_NET_BIND_SERVICE(so it can still listen on 443) and creates/var/lib/minis3and/var/log/minis3for it
The service is not enabled or started on first install, since the example
config needs editing first. Make sure the TLS certificate and key, and every
bucket path, are accessible to the minis3 user, then:
sudo systemctl enable --now minis3
Upgrades restart the service if it is running. Purging the package leaves
/var/lib/minis3 and the minis3 user in place, so bucket data is never
deleted.
Manual installation¶
examples/minis3.service
is the systemd unit the Debian package ships. To use it without the
package, install the binary as /usr/bin/minis3 (or adjust ExecStart),
create the service user, and install the unit:
sudo install -m 0755 minis3 /usr/bin/minis3
sudo useradd --system --user-group --home-dir /var/lib/minis3 \
--no-create-home --shell /usr/sbin/nologin minis3
sudo install -m 0644 examples/minis3.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now minis3
The unit runs Mini S3 as the minis3 user with only CAP_NET_BIND_SERVICE
(enough to listen on 443), and has systemd create /var/lib/minis3 and
/var/log/minis3 for it. The config file, TLS certificate and key, and
bucket paths must all be readable (buckets also writable) by that user.
ExecReload sends SIGHUP, so systemctl reload minis3 re-reads the
config.
Use -d/--daemon only if you are not using a service supervisor (it
detaches the process into a new session, redirecting stdio to /dev/null);
under systemd or similar, leave the process in the foreground and let the
supervisor manage it.
Signals¶
| Signal | Effect |
|---|---|
SIGHUP |
Re-reads the config file (buckets, credentials, log level, TLS cert/key). In-flight requests finish against the previous configuration; new requests use the new one. Also closes and reopens the log file, for log rotation (e.g. via logrotate's postrotate script sending SIGHUP). Changes to network.listen, whether TLS is enabled at all, or the metrics port/enabled flag are logged as a warning and require a restart to take effect. |
SIGTERM, SIGINT |
Graceful shutdown: stop accepting new connections, let in-flight requests finish (up to 30s), then exit. |
Logging¶
Structured JSON logs via log/slog, to
stderr by default or to general.logfile. trace and debug levels log
per-request detail (method, path, status, duration). Secrets (bucket
secret_access_key values) are never logged.
TLS¶
By default, Mini S3 terminates TLS itself and serves HTTPS directly.
network.tls.certificate should contain the full chain (leaf through
root); network.tls.key the matching private key, both PEM-encoded. The
certificate is reloaded on SIGHUP without dropping existing connections
(new TLS handshakes pick up the new certificate; the listening sockets
themselves are not rebound).
For local development, a self-signed certificate is enough:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
Why Mini S3 terminates TLS itself¶
Mini S3 is meant to be usable as a single, self-contained binary with
nothing else running alongside it — a NAS, a small VPS, a bare container.
In that shape, if Mini S3 didn't speak TLS, there would be nothing in the
path encrypting SigV4 credentials and object data before they hit the
network, and getting HTTPS at all would require standing up and operating
a second piece of software just to make the "simple, easy to configure"
service usable safely. Terminating TLS itself also means the whole request
path is encrypted end-to-end, without relying on the network between a
proxy and Mini S3 being trustworthy (relevant as soon as they're not on
the very same host). Keeping certificate/key management inside Mini S3's
own config file and SIGHUP reload also means there's only one place to
manage rotation, rather than two.
None of that makes a reverse proxy the wrong choice, though — it's a very
common and often better pattern once you already have one (centralized
certificate management, e.g. Caddy's automatic Let's Encrypt issuance;
multiplexing several backend services behind one public IP and port 443;
a WAF or rate limiting in front of Mini S3; HTTP/2 or connection pooling
handled once, centrally). That's what network.tls's optionality is for:
leave it unset and Mini S3 serves plain HTTP, expecting something else to
handle TLS in front of it.
Running behind a reverse proxy¶
Leave network.tls.certificate and network.tls.key both unset (or unset
the matching MINIS3_NETWORK_TLS_* env vars) to make Mini S3 serve plain
HTTP. Since this means requests, credentials, and object data are
unencrypted between the proxy and Mini S3, only do this when that path is
trustworthy — the proxy and Mini S3 on the same host talking over
127.0.0.1, or over a private network segment (a container network, a
WireGuard/VPN link, etc.) you control. Never expose the plain-HTTP listener
directly to an untrusted network.
[network]
listen = [":8080"] # no [network.tls] table at all
A few things the proxy needs to get right for SigV4 requests to keep validating and large uploads to keep working:
- Forward the
Hostheader unmodified. It's part of what the client signs; if the proxy rewrites it, every request will fail signature verification withSignatureDoesNotMatch. - Don't buffer or rewrite the request body.
PutObject/UploadPartbodies (including chunked/streaming ones) must reach Mini S3 byte-for-byte. - Allow large request bodies. Multipart parts and whole-object
PutObjects can be large; the proxy's default body-size limit is often too small for object storage traffic.
nginx:
server {
listen 443 ssl;
server_name s3.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
client_max_body_size 5g;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
Caddy (with automatic HTTPS — no certificate files to manage at all):
s3.example.com {
reverse_proxy 127.0.0.1:8080
request_body {
max_size 5GB
}
}
Caddy forwards the Host header and streams request bodies by default, so
no extra configuration is needed for those.