Day-to-day operations guide for running Beamdrop in production.
- Installation Steps
- Configuration Reference
- Backup and Restore
- Monitoring Setup
- Scaling Guidance
- Common Troubleshooting
Docker Compose is the simplest and most reliable deployment method.
Prerequisites: Docker 24+ and Docker Compose v2.
# Clone the repository (or copy docker-compose.yml + Caddyfile)
git clone https://github.com/ekilie/beamdrop.git
cd beamdrop
# Create your environment file
cp .env.example .env # or create one manually (see Section 2)
# Start in the background
docker compose up -d
# Verify startup
docker compose ps
docker compose logs -f beamdropWith automatic HTTPS via Caddy:
# 1. Set your domain
export BEAMDROP_DOMAIN=files.example.com
# 2. Uncomment the caddy service in docker-compose.yml
# 3. Start
docker compose up -dCaddy automatically provisions a Let's Encrypt TLS certificate.
Data is persisted in ./data/ on the host.
# Build the image
docker build -t beamdrop .
# Run with a named volume
docker run -d \
--name beamdrop \
--restart unless-stopped \
-p 7777:7777 \
-v beamdrop-data:/data \
-e BEAMDROP_PASSWORD="change-me" \
-e BEAMDROP_API_AUTH=true \
-e BEAMDROP_RATE_LIMIT=100 \
beamdrop
# Check health
curl http://localhost:7777/health/liveStep 1 Determine architecture:
uname -m
# x86_64 → AMD64
# aarch64 → ARM64Step 2 Download and install:
# AMD64
curl -L https://github.com/ekilie/beamdrop/releases/latest/download/beamdrop-linux-amd64.tar.gz \
-o beamdrop.tar.gz
# ARM64
curl -L https://github.com/ekilie/beamdrop/releases/latest/download/beamdrop-linux-arm64.tar.gz \
-o beamdrop.tar.gz
sudo tar -C /usr/local/bin -xzf beamdrop.tar.gz
rm beamdrop.tar.gz
beamdrop -v # verifyStep 3 Create a dedicated service user and data directory:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin beamdrop
sudo mkdir -p /var/lib/beamdrop
sudo chown beamdrop:beamdrop /var/lib/beamdropStep 4 Create the systemd unit:
sudo tee /etc/systemd/system/beamdrop.service > /dev/null <<'EOF'
[Unit]
Description=Beamdrop File Server
Documentation=https://github.com/ekilie/beamdrop
After=network.target
[Service]
User=beamdrop
Group=beamdrop
WorkingDirectory=/var/lib/beamdrop
ExecStart=/usr/local/bin/beamdrop \
-dir /var/lib/beamdrop \
-db-path /var/lib/beamdrop/.beamdrop/beamdrop.db \
-log-level info \
-rate-limit 100
# Environment overrides keep secrets here, not in command flags
EnvironmentFile=-/etc/beamdrop/beamdrop.env
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
# Hardening
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/beamdrop
[Install]
WantedBy=multi-user.target
EOFStep 5 Create the environment file with secrets:
sudo mkdir -p /etc/beamdrop
sudo tee /etc/beamdrop/beamdrop.env > /dev/null <<'EOF'
BEAMDROP_PASSWORD=change-me-strong-password
BEAMDROP_API_AUTH=true
BEAMDROP_PORT=7777
EOF
sudo chmod 600 /etc/beamdrop/beamdrop.env
sudo chown root:root /etc/beamdrop/beamdrop.envStep 6 Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now beamdrop
sudo systemctl status beamdropPlace Beamdrop behind Nginx for TLS termination and a proper domain name.
# /etc/nginx/sites-available/beamdrop
server {
listen 80;
server_name files.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name files.example.com;
ssl_certificate /etc/letsencrypt/live/files.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/files.example.com/privkey.pem;
# Increase for large file uploads
client_max_body_size 10G;
# WebSocket support (required for /ws/stats)
location / {
proxy_pass http://127.0.0.1:7777;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
}
}sudo ln -s /etc/nginx/sites-available/beamdrop /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Obtain TLS certificate with Certbot
sudo certbot --nginx -d files.example.comAfter enabling Nginx, close direct access to port 7777:
sudo ufw delete allow 7777
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp# Prerequisites: Go 1.21+, Node.js 18+, bun
git clone https://github.com/ekilie/beamdrop.git
cd beamdrop
# Build frontend + backend
make build
# Binary is at ./cmd/beam/beamdrop
./cmd/beam/beamdrop -vAll variables can be set in .env (Docker Compose), /etc/beamdrop/beamdrop.env (systemd), or exported directly.
| Variable | Default | Description |
|---|---|---|
BEAMDROP_PORT |
7777 |
Port to listen on |
BEAMDROP_PASSWORD |
(none) | Web UI password. Leave unset to disable auth |
BEAMDROP_LOG_LEVEL |
info |
Log verbosity: debug, info, warn, error |
BEAMDROP_RATE_LIMIT |
100 |
General rate limit in requests/min per IP (0 = disabled) |
BEAMDROP_API_AUTH |
false |
Enable S3 API key authentication |
BEAMDROP_QR |
false |
Print a QR code to the terminal at startup |
BEAMDROP_ALLOWED_ORIGINS |
(none) | Comma-separated CORS origins. Empty = CORS disabled |
BEAMDROP_DB_PATH |
<sharedDir>/.beamdrop/beamdrop.db |
SQLite database path or parent directory |
BEAMDROP_TLS_CERT |
(none) | Path to TLS certificate (PEM) |
BEAMDROP_TLS_KEY |
(none) | Path to TLS private key (PEM) |
BEAMDROP_TRUSTED_PROXIES |
(none) | Comma-separated CIDR ranges of trusted reverse proxies |
Flags override environment variables when both are set.
| Flag | Description | Default |
|---|---|---|
-dir |
Directory to share | Current directory |
-port |
Server port | Auto-detect |
-p |
Web UI password | None |
-api-auth |
Enable API key authentication | false |
-tls-cert |
Path to TLS certificate | None |
-tls-key |
Path to TLS private key | None |
-allowed-origins |
CORS allowed origins (comma-separated) | None |
-db-path |
SQLite database path or directory | ~/.beamdrop/beamdrop.db |
-rate-limit |
Rate limit in req/min per IP (0 = off) |
100 |
-trusted-proxies |
Trusted proxy CIDRs (comma-separated) | None |
-log-level |
debug / info / warn / error |
info |
-qr |
Print QR code at startup | false |
-v |
Print version and exit | |
-h |
Print help and exit |
Beamdrop applies three independent per-IP token-bucket tiers derived from the general rate (-rate-limit):
| Tier | Endpoints | Rate |
|---|---|---|
| General | All other endpoints | BEAMDROP_RATE_LIMIT req/min |
| Auth | /auth/login |
5% of general (min 1) req/min |
| Upload | /upload, S3 PUT object |
10% of general (min 1) req/min |
Self-signed certificate (development/testing):
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/C=US/ST=State/L=City/O=Org/CN=localhost"
beamdrop -dir /data -tls-cert cert.pem -tls-key key.pemLet's Encrypt (production, via Caddy):
export BEAMDROP_DOMAIN=files.example.com
# Uncomment caddy service in docker-compose.yml, then:
docker compose up -d# .env Production configuration
BEAMDROP_PORT=7777
BEAMDROP_PASSWORD=<strong-random-password>
BEAMDROP_LOG_LEVEL=info
BEAMDROP_RATE_LIMIT=100
BEAMDROP_API_AUTH=true
BEAMDROP_QR=false
BEAMDROP_ALLOWED_ORIGINS=https://files.example.com
BEAMDROP_DB_PATH=/data/.beamdrop/beamdrop.db
# TLS is handled by Caddy leave these blank if using a reverse proxy
BEAMDROP_TLS_CERT=
BEAMDROP_TLS_KEY=
BEAMDROP_TRUSTED_PROXIES=127.0.0.1/32,172.16.0.0/12Beamdrop stores state in two locations:
| Location | Contents | Must Back Up? |
|---|---|---|
<sharedDir>/ |
Uploaded files, buckets | Yes |
<sharedDir>/.beamdrop/beamdrop.db (or BEAMDROP_DB_PATH) |
API keys, shareable links, starred files, stats | Yes |
<sharedDir>/.beamdrop/beamdrop.log |
Structured JSON logs | Optional |
<sharedDir>/.beamdrop_trash/ |
Deleted files (recoverable) | Optional |
# Stop the service first for a consistent snapshot (or use SQLite hot backup below)
sudo systemctl stop beamdrop
# Create a timestamped archive
BACKUP_DIR=/backups/beamdrop
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/beamdrop-data-$DATE.tar.gz" /var/lib/beamdrop/
sudo systemctl start beamdrop
echo "Backup saved: $BACKUP_DIR/beamdrop-data-$DATE.tar.gz"The SQLite .backup command creates a consistent copy while the server is running:
sqlite3 /var/lib/beamdrop/.beamdrop/beamdrop.db \
".backup /backups/beamdrop/beamdrop-$(date +%Y%m%d_%H%M%S).db"sudo tee /usr/local/bin/beamdrop-backup.sh > /dev/null <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
DATA_DIR=/var/lib/beamdrop
DB_PATH="$DATA_DIR/.beamdrop/beamdrop.db"
BACKUP_DIR=/backups/beamdrop
KEEP_DAYS=14
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)
# 1. Live database backup (no downtime)
sqlite3 "$DB_PATH" ".backup $BACKUP_DIR/beamdrop-db-$DATE.db"
# 2. Archive all uploaded files
tar -czf "$BACKUP_DIR/beamdrop-files-$DATE.tar.gz" \
--exclude="$DATA_DIR/.beamdrop" \
"$DATA_DIR/"
# 3. Remove backups older than KEEP_DAYS
find "$BACKUP_DIR" -name "beamdrop-*.tar.gz" -mtime +"$KEEP_DAYS" -delete
find "$BACKUP_DIR" -name "beamdrop-*.db" -mtime +"$KEEP_DAYS" -delete
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) Backup complete: $BACKUP_DIR"
SCRIPT
sudo chmod +x /usr/local/bin/beamdrop-backup.sh
# Run daily at 2 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/beamdrop-backup.sh >> /var/log/beamdrop-backup.log 2>&1") \
| crontab -# Stop the container first
docker compose down
# Archive the volume-mapped data directory
tar -czf "beamdrop-data-$(date +%Y%m%d_%H%M%S).tar.gz" ./data/
# Restart
docker compose up -dFor zero-downtime database backup while the container is running:
docker exec beamdrop sqlite3 /data/.beamdrop/beamdrop.db \
".backup /data/.beamdrop/beamdrop-backup.db"
docker cp beamdrop:/data/.beamdrop/beamdrop-backup.db \
"./beamdrop-db-$(date +%Y%m%d_%H%M%S).db"Full restore from archive:
sudo systemctl stop beamdrop
# Restore data directory (adjust path to your backup file)
sudo tar -xzf /backups/beamdrop/beamdrop-files-20260101_020000.tar.gz \
-C /
# Restore database
cp /backups/beamdrop/beamdrop-db-20260101_020000.db \
/var/lib/beamdrop/.beamdrop/beamdrop.db
sudo chown -R beamdrop:beamdrop /var/lib/beamdrop
sudo systemctl start beamdropDatabase-only restore:
sudo systemctl stop beamdrop
cp /backups/beamdrop/beamdrop-db-<TIMESTAMP>.db \
/var/lib/beamdrop/.beamdrop/beamdrop.db
sudo chown beamdrop:beamdrop /var/lib/beamdrop/.beamdrop/beamdrop.db
sudo systemctl start beamdropBeamdrop provides Kubernetes-compatible health probes. These require no authentication.
| Endpoint | Purpose | Use Case |
|---|---|---|
GET /health |
Full health overview (all components) | Manual checks, dashboards |
GET /health/live |
Liveness process is running (no I/O) | K8s livenessProbe |
GET /health/ready |
Readiness DB + storage accessible | K8s readinessProbe |
GET /health/startup |
Startup complete | K8s startupProbe |
Example response (/health):
{
"status": "healthy",
"service": "beamdrop",
"version": "1.0.0",
"timestamp": "2026-01-01T00:00:00Z",
"components": {
"process": { "status": "ok", "message": "running" },
"startup": { "status": "ok", "message": "initialisation complete" },
"database": { "status": "ok", "message": "connected", "latency": "1.23ms" },
"storage": { "status": "ok", "message": "writable" },
"runtime": { "status": "ok", "message": "goroutines: 12" }
}
}Quick health check script:
#!/usr/bin/env bash
STATUS=$(curl -sf http://localhost:7777/health/live && echo "UP" || echo "DOWN")
echo "Beamdrop: $STATUS"Beamdrop exposes a /metrics endpoint in Prometheus text format (no authentication required).
Key metrics:
| Metric | Type | Description |
|---|---|---|
beamdrop_requests_total |
counter | HTTP requests by method, path, status |
beamdrop_request_duration_seconds |
histogram | Request latency (p50/p95/p99) |
beamdrop_auth_failures_total |
counter | Auth failures by reason |
beamdrop_uploads_total |
counter | Completed uploads |
beamdrop_downloads_total |
counter | Completed downloads |
beamdrop_upload_size_bytes |
histogram | Upload file sizes |
beamdrop_storage_bytes |
gauge | Bytes used by stored files |
beamdrop_objects_total |
gauge | Number of stored files |
beamdrop_active_connections |
gauge | In-flight HTTP requests |
beamdrop_storage_free_bytes |
gauge | Free disk space |
beamdrop_storage_total_bytes |
gauge | Total disk capacity |
beamdrop_goroutines_count |
gauge | Go goroutine count |
Add Beamdrop as a Prometheus scrape target:
# prometheus.yml
scrape_configs:
- job_name: beamdrop
static_configs:
- targets: ["localhost:7777"]
# Optional basic auth if behind a proxy that requires it
# basic_auth:
# username: prometheus
# password: secretA pre-built Grafana dashboard is included at docs/grafana-dashboard.json.
Import steps:
- Open Grafana → Dashboards → Import
- Click Upload JSON file and select
docs/grafana-dashboard.json - Select your Prometheus data source
- Click Import
livenessProbe:
httpGet:
path: /health/live
port: 7777
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 7777
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
startupProbe:
httpGet:
path: /health/startup
port: 7777
failureThreshold: 30
periodSeconds: 2# beamdrop-alerts.yml
groups:
- name: beamdrop
rules:
- alert: BeamdropDown
expr: up{job="beamdrop"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Beamdrop instance is down"
- alert: BeamdropHighErrorRate
expr: |
rate(beamdrop_requests_total{status=~"5.."}[5m])
/ rate(beamdrop_requests_total[5m]) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "Beamdrop error rate > 5%"
- alert: BeamdropDiskAlmostFull
expr: |
beamdrop_storage_free_bytes / beamdrop_storage_total_bytes < 0.10
for: 10m
labels:
severity: warning
annotations:
summary: "Beamdrop storage less than 10% free"
- alert: BeamdropHighLatency
expr: |
histogram_quantile(0.95,
rate(beamdrop_request_duration_seconds_bucket[5m])
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "Beamdrop p95 latency > 2s"Beamdrop writes structured JSON logs to <sharedDir>/.beamdrop/beamdrop.log.
Follow logs (systemd):
journalctl -u beamdrop -fFollow logs (Docker):
docker compose logs -f beamdropQuery logs with jq:
# Show errors only
tail -f /var/lib/beamdrop/.beamdrop/beamdrop.log | jq 'select(.level=="ERROR")'
# Count requests by status in the last hour
jq -r 'select(.msg | test("request")) | .status' \
/var/lib/beamdrop/.beamdrop/beamdrop.log | sort | uniq -cShipping logs to a centralised system (e.g., Loki via Promtail):
# promtail-config.yaml
scrape_configs:
- job_name: beamdrop
static_configs:
- targets: [localhost]
labels:
job: beamdrop
__path__: /var/lib/beamdrop/.beamdrop/beamdrop.log
pipeline_stages:
- json:
expressions:
level: level
msg: msg
- labels:
level:Beamdrop is a single-process server backed by an embedded SQLite database. The following guidance will help you maximise capacity within that architecture.
The simplest way to handle more load. Beamdrop's Go runtime makes good use of multiple CPU cores for concurrent request handling.
| Resource | Recommendation |
|---|---|
| CPU | 2+ cores for moderate traffic; 4+ cores for high concurrency |
| RAM | 512 MB minimum; 2 GB+ for large file transfers |
| Disk I/O | Use SSD/NVMe; IOPS matter more than throughput for small files |
| Network | 1 Gbps for general use; 10 Gbps for sustained large uploads |
Monitor goroutine count (beamdrop_goroutines_count) a continuously rising count may indicate a leak; a spike under load is normal.
# Add a larger disk and move the data directory
sudo systemctl stop beamdrop
sudo rsync -a /var/lib/beamdrop/ /mnt/new-disk/beamdrop/
# Update ExecStart -dir flag or BEAMDROP_DB_PATH, then:
sudo systemctl start beamdropRecommended filesystem:
-
ext4orxfson Linux both handle large directories well -
Enable
noatimemount option to reduce write overhead:# /etc/fstab /dev/sdb1 /var/lib/beamdrop ext4 defaults,noatime 0 2
When Beamdrop is behind Nginx or Caddy, tune the proxy for large files:
Nginx:
# Increase for large file uploads
client_max_body_size 10G;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_request_buffering off; # stream uploads directly to backendCaddy:
{$BEAMDROP_DOMAIN} {
request_body {
max_size 10GB
}
reverse_proxy beamdrop:7777 {
transport http {
read_timeout 600s
write_timeout 600s
}
}
}Adjust the rate limit to balance protection against abuse vs. legitimate user throughput. As a starting point:
| Traffic Level | BEAMDROP_RATE_LIMIT |
|---|---|
| Personal / low | 50 |
| Team (< 20 users) | 100 (default) |
| Departmental | 300 |
| CI/CD heavy usage | 500–1000 |
| Disable (trusted network) | 0 |
SQLite supports multiple readers. If your workload is read-heavy (many downloads), you can serve reads from a replica by periodically copying the .db file:
# On a read replica node refresh every 60 seconds
while true; do
rsync -a beamdrop-primary:/var/lib/beamdrop/.beamdrop/beamdrop.db \
/var/lib/beamdrop/.beamdrop/beamdrop.db
sleep 60
doneNote: Writes must still go to the primary. A load balancer (e.g., HAProxy) should route mutating requests (
POST,PUT,DELETE) to the primary andGETrequests to replicas.
For high-availability deployments, run multiple Beamdrop instances pointing to a shared NFS/CIFS volume:
# On each node
beamdrop \
-dir /mnt/nfs/beamdrop-data \
-db-path /mnt/nfs/beamdrop-data/.beamdrop/beamdrop.db \
-port 7777Place a load balancer (Nginx upstream, Caddy, HAProxy) in front:
upstream beamdrop {
least_conn;
server node1:7777;
server node2:7777;
server node3:7777;
keepalive 32;
}Important: SQLite in WAL mode handles concurrent writers from multiple processes, but with NFS you must test carefully. For heavy write loads, consider migrating to an external database via a future release.
Symptom: systemctl status beamdrop shows Active: failed
Steps:
# 1. Check the full error message
journalctl -u beamdrop -n 50 --no-pager
# 2. Verify the binary is executable
ls -la /usr/local/bin/beamdrop
# 3. Test the binary manually as the service user
sudo -u beamdrop /usr/local/bin/beamdrop -dir /var/lib/beamdrop -v
# 4. Check file/directory permissions
ls -la /var/lib/beamdrop
sudo chown -R beamdrop:beamdrop /var/lib/beamdrop
# 5. Check if the port is already in use
sudo ss -tlnp | grep 7777Symptom: listen tcp :7777: bind: address already in use
# Find and stop the conflicting process
sudo ss -tlnp | grep 7777
sudo kill <PID>
# Or use a different port
export BEAMDROP_PORT=8888Symptom: Correct password rejected; browser shows 401.
# 1. Confirm password is set in environment file
grep BEAMDROP_PASSWORD /etc/beamdrop/beamdrop.env
# 2. Restart to reload the environment
sudo systemctl restart beamdrop
# 3. Check auth failure metrics
curl -s http://localhost:7777/metrics | grep auth_failuresSymptom: 403 Forbidden or 401 Unauthorized on S3 API calls.
# 1. Verify API auth is enabled
curl -s http://localhost:7777/health | jq .
# 2. Check that the access key and secret match what was generated
# (the secret is shown only once at key creation)
# 3. Verify the clock skew the HMAC timestamp must be within 15 minutes
date -u
# 4. Regenerate the signature with the correct timestamp format: 2006-01-02T15:04:05Z
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Timestamp: $TIMESTAMP"Symptom: Upload errors, timeouts, or "413 Request Entity Too Large".
# 1. Check available disk space
df -h /var/lib/beamdrop
# 2. Verify directory is writable by the service user
sudo -u beamdrop touch /var/lib/beamdrop/test-write && \
sudo -u beamdrop rm /var/lib/beamdrop/test-write && \
echo "Writable"
# 3. If behind Nginx, increase client_max_body_size
sudo nano /etc/nginx/sites-available/beamdrop
# Add/update: client_max_body_size 10G;
sudo nginx -t && sudo systemctl reload nginx
# 4. Check upload rate limit
curl -s http://localhost:7777/metrics | grep rate_limitSymptom: HTTP 500 on uploads; no space left on device in logs; or HTTP 507 STORAGE_FULL when -max-storage limit is reached.
# 1. Check disk usage
df -h /var/lib/beamdrop
du -sh /var/lib/beamdrop/*
# 2. Check if max-storage limit is set
grep MAX_STORAGE /etc/beamdrop/beamdrop.env
# 3. Increase the limit or set to 0 (unlimited)
BEAMDROP_MAX_STORAGE=10GB # in /etc/beamdrop/beamdrop.env
sudo systemctl restart beamdrop
# 4. Recover space from trash (permanently delete trashed files)
sudo rm -rf /var/lib/beamdrop/.beamdrop_trash/*
# 3. Compress or archive old files in buckets
du -sh /var/lib/beamdrop/buckets/* | sort -hr | head -20
# 4. Expand the volume (cloud provider) or attach additional storageSymptom: Legitimate users receive 429 Too Many Requests.
# 1. Check current limit
beamdrop -v && grep RATE_LIMIT /etc/beamdrop/beamdrop.env
# 2. Increase the limit or disable it
BEAMDROP_RATE_LIMIT=500 # in /etc/beamdrop/beamdrop.env
sudo systemctl restart beamdrop
# 3. Verify the rate limit tier breakdown
curl -s http://localhost:7777/metrics | grep rateSymptom: database is locked, unable to open database file, or intermittent 500 errors.
# 1. Check that only one Beamdrop process is running
pgrep -a beamdrop
# 2. Verify the database file exists and is readable
ls -la /var/lib/beamdrop/.beamdrop/beamdrop.db
# 3. Check SQLite integrity
sqlite3 /var/lib/beamdrop/.beamdrop/beamdrop.db "PRAGMA integrity_check;"
# 4. If corrupt restore from latest backup (see Section 3.5)
# 5. Enable WAL mode for better concurrent access
sqlite3 /var/lib/beamdrop/.beamdrop/beamdrop.db "PRAGMA journal_mode=WAL;"Symptom: certificate signed by unknown authority, certificate has expired, or HTTPS not working.
# 1. Check certificate expiry
openssl x509 -in /etc/beamdrop/cert.pem -noout -dates
# 2. Verify the key matches the certificate
openssl x509 -in /etc/beamdrop/cert.pem -noout -modulus | md5sum
openssl rsa -in /etc/beamdrop/key.pem -noout -modulus | md5sum
# Both MD5 hashes must match
# 3. Renew Let's Encrypt certificate (Certbot)
sudo certbot renew --nginx
sudo systemctl reload nginx
# 4. If using Caddy, certificates are renewed automatically
docker compose logs caddy | grep -i certSymptom: Load balancer marks the instance as unhealthy; /health/ready returns 503.
# 1. Check all components
curl -s http://localhost:7777/health | jq .
# 2. Database component unhealthy
sqlite3 /var/lib/beamdrop/.beamdrop/beamdrop.db "SELECT 1;"
# 3. Storage component unhealthy check directory permissions
ls -la /var/lib/beamdrop/
# 4. Restart the service
sudo systemctl restart beamdrop
# 5. Check startup probe if the container was just launched
curl -s http://localhost:7777/health/startup | jq .Symptom: Log file is empty or missing.
# Default log file location
ls -la /var/lib/beamdrop/.beamdrop/beamdrop.log
# Verify log level is not set too high
grep LOG_LEVEL /etc/beamdrop/beamdrop.env
# Tail the systemd journal as a fallback
journalctl -u beamdrop -f
# Docker logs
docker compose logs -f beamdrop# systemd
sudo systemctl start beamdrop
sudo systemctl stop beamdrop
sudo systemctl restart beamdrop
sudo systemctl status beamdrop
journalctl -u beamdrop -f
# Docker Compose
docker compose up -d
docker compose down
docker compose restart beamdrop
docker compose logs -f beamdropcurl http://localhost:7777/health/live # liveness
curl http://localhost:7777/health/ready # readiness
curl http://localhost:7777/health # full overview
curl http://localhost:7777/metrics # Prometheus metrics# Live database backup
sqlite3 /var/lib/beamdrop/.beamdrop/beamdrop.db \
".backup /backups/beamdrop/beamdrop-$(date +%Y%m%d).db"
# Full archive
tar -czf /backups/beamdrop/beamdrop-$(date +%Y%m%d).tar.gz /var/lib/beamdrop/