Update DSM:
# Always run latest stable DSM version
# Control Panel → Update & Restore → DSM UpdateEnable Security Features:
- ✅ Enable auto-block for failed login attempts
- ✅ Enable firewall (allow only necessary ports)
- ✅ Disable unused services (FTP, Telnet, etc.)
- ✅ Enable account protection
- ✅ Set up 2FA for DSM admin account
- ✅ Enable notifications for security events
Firewall Configuration:
Allow from All Sources:
- Port 80/TCP (HTTP - will redirect to HTTPS)
- Port 443/TCP (HTTPS)
- Port 51820/UDP (WireGuard VPN)
Allow from Local Network Only:
- Port 22/TCP (SSH)
- Port 5000/TCP (DSM HTTP)
- Port 5001/TCP (DSM HTTPS)
- Port 9000/TCP (Portainer)
Block all other incoming traffic
SSH Hardening:
# Edit SSH config
sudo vi /etc/ssh/sshd_config
# Recommended settings:
PermitRootLogin no
PasswordAuthentication no # Use SSH keys only
PubkeyAuthentication yes
Port 22222 # Change default port
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers yourusername # Limit to specific users
# Restart SSH
sudo synoservicectl --reload sshdDocker Daemon Configuration:
# On Synology, edit /var/packages/Docker/etc/dockerd.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true
}
# Restart Docker
sudo synoservicectl --restart pkgctl-DockerUser Namespaces:
# Enable user namespace remapping for additional isolation
# This maps container root to non-privileged host userSecurity Headers (already configured):
headers:
frameDeny: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: trueAdditional Hardening:
# Add to dynamic/middlewares.yml
http:
middlewares:
# Restrict access to specific countries (example)
geoblock:
ipWhiteList:
sourceRange:
- "127.0.0.1/32" # Localhost
- "192.168.1.0/24" # Local network
# Add your country's IP ranges if needed
# Advanced rate limiting
rate-limit-aggressive:
rateLimit:
average: 10
burst: 5
period: 1m
sourceCriterion:
ipStrategy:
depth: 1Disable Dashboard in Production:
# In traefik.yml
api:
dashboard: false # Set to false after initial setup
insecure: falseStrong Password Hashing:
# Already configured with Argon2id
password:
algorithm: argon2id
iterations: 3
key_length: 32
salt_length: 16
memory: 65536 # 64MB
parallelism: 4Session Security:
session:
secret: USE_ENV_VARIABLE
expiration: 1h # Short session timeout
inactivity: 5m # Auto-logout after inactivity
remember_me_duration: 1M
cookies:
- domain: yourdomain.com
authelia_url: https://auth.yourdomain.com
default_redirection_url: https://yourdomain.com
secure: true # HTTPS only
same_site: laxAccess Control Rules:
access_control:
default_policy: deny # Deny by default
rules:
# Public access to auth portal
- domain: 'auth.yourdomain.com'
policy: bypass
# Bypass for monitoring (internal network only)
- domain: 'status.yourdomain.com'
policy: bypass
networks:
- '192.168.1.0/24'
# Admin services require 2FA
- domain:
- 'portainer.yourdomain.com'
- 'grafana.yourdomain.com'
policy: two_factor
subject:
- 'group:admins'
# All other services require 2FA
- domain: '*.yourdomain.com'
policy: two_factorFailed Authentication Protection:
regulation:
max_retries: 3
find_time: 2m
ban_time: 10m # Increase from defaultDisable Registration:
environment:
- SIGNUPS_ALLOWED=false # Critical!
- SIGNUPS_VERIFY=true
- INVITATIONS_ALLOWED=true # Control user creation
- ADMIN_TOKEN=${STRONG_RANDOM_TOKEN}Additional Security:
environment:
- DOMAIN=https://vault.${DOMAIN}
- LOG_LEVEL=warn
- EXTENDED_LOGGING=true
- LOG_FILE=/data/vaultwarden.log
- WEBSOCKET_ENABLED=true
- WEB_VAULT_ENABLED=true
- DISABLE_ICON_DOWNLOAD=false
- IP_HEADER=X-Forwarded-For
- ADMIN_RATELIMIT_SECONDS=300
- ADMIN_RATELIMIT_MAX_BURST=3Secure Configuration:
# config/wireguard/wg0.conf
[Interface]
Address = 10.13.13.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Client configurations
[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.13.13.2/32
PersistentKeepalive = 25Firewall Rules:
# Only allow WireGuard VPN port from internet
# All other access should be through VPN tunnelPostgreSQL (Immich, Paperless):
environment:
- POSTGRES_PASSWORD=${STRONG_PASSWORD}
- POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
command:
- "postgres"
- "-c"
- "ssl=off" # Internal network, SSL via Traefik
- "-c"
- "log_connections=on"
- "-c"
- "log_disconnections=on"
- "-c"
- "log_duration=on"MariaDB (Nextcloud):
environment:
- MYSQL_ROOT_PASSWORD=${STRONG_PASSWORD}
- MYSQL_PASSWORD=${STRONG_PASSWORD}
command:
- "--skip-name-resolve" # Prevent DNS lookups
- "--skip-networking=false"
- "--bind-address=0.0.0.0"Trusted Domains & Proxies:
// config/config.php (auto-generated, but verify)
'trusted_domains' => [
'cloud.yourdomain.com',
'192.168.1.100',
],
'trusted_proxies' => ['172.20.0.0/16'],
'overwriteprotocol' => 'https',
'overwritehost' => 'cloud.yourdomain.com',Additional Security Settings:
# Execute in Nextcloud container
docker exec -u www-data nextcloud php occ config:system:set auth.bruteforce.protection.enabled --value true --type boolean
docker exec -u www-data nextcloud php occ config:system:set loglevel --value 2
docker exec -u www-data nextcloud php occ config:system:set log_type --value file
docker exec -u www-data nextcloud php occ security:bruteforce:reset YOUR_IPNetwork Segmentation:
networks:
proxy: # Public-facing services via Traefik
storage: # Database and storage services (isolated)
monitoring: # Monitoring stack (isolated)
homeautomation: # HA and MQTT (isolated)
internal: # Completely internal (no external access)
internal: truePrevent Container-to-Container Attacks:
# Default deny, explicit allow
networks:
storage:
driver: bridge
driver_opts:
com.docker.network.bridge.enable_icc: "false"Only Expose Necessary Ports:
# Good - expose to Docker network only
expose:
- 8080
# Careful - expose to host
ports:
- "127.0.0.1:8080:8080" # Localhost only
# Avoid - expose to world
ports:
- "8080:8080" # All interfacesNever Commit Secrets:
# .gitignore
.env
*.env
!.env.example
config/**/secrets.yml
*.key
*.pemUse Strong Secrets:
# Generate strong passwords
openssl rand -hex 32 # 64-character hex
openssl rand -base64 48 # 64-character base64
pwgen -s 32 1 # 32-character alphanumeric
# Generate for Authelia users
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD'Rotate Secrets Regularly:
# Quarterly rotation schedule:
# 1. Generate new secrets
# 2. Update .env file
# 3. Restart affected services
# 4. Verify functionality
# 5. Revoke old secrets# Restrict access to sensitive files
chmod 600 .env
chmod 600 config/traefik/acme/acme.json
chmod 600 config/authelia/users_database.yml
chmod 700 scripts/*.sh
# Ensure proper ownership
chown -R 1000:1000 config/
chown -R 1000:1000 data/Centralized Logging:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service,environment"Important Events to Monitor:
- Failed login attempts (Authelia, Nextcloud, etc.)
- Unusual traffic patterns (Traefik access logs)
- Container restarts (Docker events)
- Resource exhaustion (Prometheus alerts)
- Certificate issues (Traefik logs)
Create Alert Rules:
# config/prometheus/alerts/security.yml
groups:
- name: security_alerts
interval: 30s
rules:
- alert: HighAuthFailureRate
expr: rate(authelia_authentication_failed_total[5m]) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "High authentication failure rate"
- alert: ContainerDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Container {{ $labels.instance }} is down"
- alert: HighMemoryUsage
expr: container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Container {{ $labels.name }} using >90% memory"Install on Synology:
# Monitor Traefik access logs for attack patterns
# Ban IPs with excessive failed attempts
[traefik-auth]
enabled = true
port = http,https
filter = traefik-auth
logpath = /volume1/docker/synology-stack/logs/traefik/access.log
maxretry = 5
findtime = 600
bantime = 86400Monthly Security Review:
- Review all user accounts and access levels
- Check for unauthorized containers or services
- Verify SSL certificates are valid and not expiring
- Review authentication logs for suspicious activity
- Check for container and image updates
- Verify backup integrity and test restore
- Review firewall rules
- Scan for vulnerable containers (see below)
Container Vulnerability Scanning:
# Install Trivy
wget https://github.com/aquasecurity/trivy/releases/latest/download/trivy_Linux-64bit.tar.gz
tar zxvf trivy_Linux-64bit.tar.gz
# Scan all images
docker images --format "{{.Repository}}:{{.Tag}}" | while read image; do
echo "Scanning $image..."
./trivy image --severity HIGH,CRITICAL $image
doneGDPR Considerations:
# Document in your privacy policy:
1. What data is collected (logs, user files, etc.)
2. Where data is stored (NAS location, backups)
3. Data retention periods
4. Data protection measures (encryption, access control)
5. User rights (access, deletion, portability)Data Protection:
# Encrypt sensitive backups
tar -czf - backups/ | openssl enc -aes-256-cbc -salt -out backups-encrypted.tar.gz.enc
# Decrypt when needed
openssl enc -d -aes-256-cbc -in backups-encrypted.tar.gz.enc | tar -xzf -1. Detection:
- Monitor Uptime Kuma for service disruptions
- Review Grafana dashboards for anomalies
- Check Authelia logs for brute force attempts
- Review Traefik access logs for suspicious patterns
2. Containment:
# Immediately isolate affected container
docker network disconnect proxy compromised-container
# Stop the container if necessary
docker stop compromised-container
# Block attacking IPs at firewall level3. Investigation:
# Examine container logs
docker logs compromised-container > incident-logs.txt
# Check for file modifications
docker diff compromised-container
# Export container for forensics
docker export compromised-container > container-forensics.tar4. Recovery:
# Restore from clean backup
./scripts/restore.sh service-name
# Rotate all secrets
# Update .env with new passwords
docker-compose up -d
# Verify integrity
./scripts/health-check.sh5. Post-Incident:
- Document what happened
- Update security measures to prevent recurrence
- Review and update incident response procedures
Daily (Automated):
# Automated backup
0 2 * * * /volume1/docker/synology-stack/scripts/backup.sh
# Log review (automated via monitoring)Weekly:
# Check for security updates
docker-compose pull
# Review authentication logs
grep "Failed" logs/traefik/access.log | tail -100
# Verify all services running
./scripts/health-check.shMonthly:
# Security scan
./trivy image $(docker images --format "{{.Repository}}:{{.Tag}}")
# Access audit
# Review user accounts in Authelia, Nextcloud, etc.
# Certificate check
openssl s_client -connect yourdomain.com:443 -servername yourdomain.comQuarterly:
# Penetration testing (if feasible)
# Password rotation
# Review and update firewall rules
# Disaster recovery testStay Informed:
- Subscribe to security advisories for all used software
- Follow Docker security best practices
- Monitor CVE databases for vulnerabilities
- Join Synology community forums
Tools:
- Trivy - Container vulnerability scanning
- Docker Bench Security - Docker security audit
- OWASP Top 10 - Web application security
Remember: Security is an ongoing process, not a one-time setup. Regular monitoring, updates, and audits are essential.