Secret Rotation Guide
This guide documents procedures for rotating each secret type in Pavillion. Regular secret rotation is a security best practice and should be performed periodically or immediately if a secret is suspected to be compromised.
Missing secret after an upgrade
If a new Pavillion version adds a required secret that your .env does not have, bin/deploy.sh handles it automatically:
regenerablesecrets — silently generated.stablesecrets — prompted (interactive) or hard-failed (non-interactive) with an actionable error.
See docs/upgrading.md for the full flow.
This document covers a different case: intentional rotation of an existing secret. That remains a manual procedure, described below.
Overview
Pavillion uses five types of secrets:
| Secret | Purpose | Rotation Impact |
|---|---|---|
JWT_SECRET | Signs API authentication tokens | All API tokens invalidated |
SESSION_SECRET | Signs browser session cookies | All sessions invalidated |
DB_PASSWORD | PostgreSQL database access | Brief downtime during rotation |
S3_SECRET_KEY | S3 storage access | Media access interruption if misconfigured |
SMTP_PASSWORD | Email sending | Email delivery paused during rotation |
Before You Begin
- Backup current secrets to your password manager
- Schedule maintenance window for secrets that cause downtime
- Notify users if rotation will log them out
- Have rollback plan in case of issues
JWT_SECRET Rotation
Impact
- All existing API tokens become invalid
- Users must log in again to get new tokens
- Mobile/API clients will receive 401 errors until re-authenticated
- No downtime required
Procedure
Generate new secret:
bashNEW_JWT_SECRET=$(openssl rand -base64 32) echo "New JWT_SECRET: $NEW_JWT_SECRET"Save to password manager before proceeding
Update
.envfile:bash# Edit .env and replace JWT_SECRET value sed -i "s/JWT_SECRET=.*/JWT_SECRET=$NEW_JWT_SECRET/" .envUpdate secrets file (if using Docker secrets):
bashecho -n "$NEW_JWT_SECRET" > secrets/jwt_secret.txt chmod 600 secrets/jwt_secret.txtRestart the application:
bashdocker compose restart appVerify rotation:
bash# Check logs for successful startup docker compose logs app | tail -20 # Test API authentication (should require new login) curl -I http://localhost:3000/api/v1/accounts/me
Rollback
If issues occur, restore the previous JWT_SECRET from your password manager and restart.
SESSION_SECRET Rotation
Impact
- All existing browser sessions become invalid
- Users must log in again
- Session cookies will be rejected
- No downtime required
Procedure
Generate new secret:
bashNEW_SESSION_SECRET=$(openssl rand -base64 32) echo "New SESSION_SECRET: $NEW_SESSION_SECRET"Save to password manager before proceeding
Update
.envfile:bashsed -i "s/SESSION_SECRET=.*/SESSION_SECRET=$NEW_SESSION_SECRET/" .envUpdate secrets file (if using Docker secrets):
bashecho -n "$NEW_SESSION_SECRET" > secrets/session_secret.txt chmod 600 secrets/session_secret.txtRestart the application:
bashdocker compose restart appVerify rotation:
bash# Check logs for successful startup docker compose logs app | tail -20 # Visit the application in browser - should require login
Rollback
Restore the previous SESSION_SECRET from your password manager and restart.
DB_PASSWORD Rotation
Impact
- Brief downtime while rotating (typically < 1 minute)
- Application cannot connect until both database and app use new password
- No user-facing session impact (existing sessions remain valid)
Procedure
Generate new password:
bashNEW_DB_PASSWORD=$(openssl rand -base64 32) echo "New DB_PASSWORD: $NEW_DB_PASSWORD"Save to password manager before proceeding
Stop the application:
bashdocker compose stop appUpdate PostgreSQL password:
bash# Connect to database container docker compose exec db psql -U pavillion -d pavillion -c \ "ALTER USER pavillion WITH PASSWORD '$NEW_DB_PASSWORD';"Update
.envfile:bashsed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$NEW_DB_PASSWORD/" .envUpdate secrets file (if using Docker secrets):
bashecho -n "$NEW_DB_PASSWORD" > secrets/db_password.txt chmod 600 secrets/db_password.txtStart the application:
bashdocker compose start appVerify rotation:
bash# Check logs for successful database connection docker compose logs app | grep -i "database\|postgres\|ready" # Test application functionality curl http://localhost:3000/health
Rollback
If the application cannot connect:
- Stop the application:
docker compose stop app - Reset password in PostgreSQL to the previous value
- Restore previous DB_PASSWORD in
.envand secrets file - Start the application:
docker compose start app
S3_SECRET_KEY Rotation
Impact
- Media uploads/downloads may fail during rotation
- No downtime if done correctly
- Existing media remains accessible if bucket permissions unchanged
Prerequisites
- Access to your S3/cloud storage provider console
- New access key generated in provider dashboard
Procedure
Generate new access key in your cloud provider:
- AWS: IAM Console > Users > Security credentials > Create access key
- DigitalOcean: API > Spaces access keys > Generate new key
- MinIO: Admin console > Access Keys > Create access key
Save new credentials to password manager
Test new credentials (optional but recommended):
bash# Using AWS CLI AWS_ACCESS_KEY_ID=new_key AWS_SECRET_ACCESS_KEY=new_secret \ aws s3 ls s3://your-bucket-name/Update
.envfile:bash# Edit .env with new S3_ACCESS_KEY and S3_SECRET_KEYUpdate secrets file (if using Docker secrets):
bashecho -n "your_new_secret_key" > secrets/s3_secret_key.txt chmod 600 secrets/s3_secret_key.txtRestart the application:
bashdocker compose restart appVerify rotation:
bash# Upload a test image through the application # Check logs for S3 errors docker compose logs app | grep -i "s3\|storage"Revoke old access key in your cloud provider console
Rollback
If media access fails:
- Restore old S3_ACCESS_KEY and S3_SECRET_KEY in
.env - Restart application
- Delete the new access key in provider console (if created)
SMTP_PASSWORD Rotation
Impact
- Email sending pauses during rotation
- No emails delivered until new password is configured
- No downtime for the application itself
- Queued emails may fail (depends on email provider)
Prerequisites
- Access to your email provider/SMTP service dashboard
- New SMTP password or app-specific password
Procedure
Generate/obtain new SMTP password:
- Check your email provider's documentation
- For Gmail/Google Workspace: Create new app password
- For transactional email services (SendGrid, Mailgun): Generate new API key
Save new password to password manager
Update
.envfile:bash# Edit .env with new SMTP_PASSWORDUpdate secrets file (if using Docker secrets):
bashecho -n "your_new_smtp_password" > secrets/smtp_password.txt chmod 600 secrets/smtp_password.txtRestart the application:
bashdocker compose restart appVerify rotation:
bash# Trigger a test email (e.g., password reset request) # Check application logs for SMTP errors docker compose logs app | grep -i "smtp\|mail\|email"Revoke old password (if applicable)
Rollback
If email sending fails:
- Restore old SMTP_PASSWORD in
.envand secrets file - Restart application
- Delete new password/key if created in email provider
Rotation Schedule Recommendations
| Secret | Recommended Frequency | Emergency Rotation |
|---|---|---|
JWT_SECRET | Annually | If token theft suspected |
SESSION_SECRET | Annually | If session hijacking suspected |
DB_PASSWORD | Annually | If database credentials exposed |
S3_SECRET_KEY | Annually | If key exposed or employee departure |
SMTP_PASSWORD | Annually | If email account compromised |
Post-Rotation Checklist
After rotating any secret:
- [ ] New secret saved in password manager
- [ ] Application restarted and running normally
- [ ] Functionality verified (login, media, email as applicable)
- [ ] Old credentials revoked (where applicable)
- [ ] Rotation documented with date in your operations log
Emergency Rotation
If you suspect a secret has been compromised:
- Rotate immediately - don't wait for maintenance window
- Review access logs for unauthorized activity
- Check for unauthorized changes to calendars/events
- Consider notifying users if their sessions/tokens were at risk
- Document the incident for future reference
Automation Considerations
For organizations requiring automated rotation:
- Consider using HashiCorp Vault or similar secret management tools
- Implement monitoring for secret age and rotation compliance
- Set up alerts for rotation failures
- Document automation procedures separately
Note: Automated rotation is beyond the scope of this guide and requires additional infrastructure.