Follow these security best practices to harden your Ory Kratos deployment for production use.
Production checklist
Before going to production, verify these security settings:
Never use --dev flag
The --dev flag disables critical security features: # ❌ NEVER in production
kratos serve --dev
# ✅ Production
kratos serve -c /etc/kratos/config.yml
The --dev flag disables CSRF protection, uses insecure cookies, and enables other development-only features.
Use HTTPS everywhere
Always use HTTPS for all Kratos endpoints: serve :
public :
base_url : https://auth.example.com/ # ✅
# NOT http://auth.example.com/ ❌
admin :
base_url : https://admin.example.com/ # ✅
Generate strong secrets
Use cryptographically secure random secrets (32+ characters): secrets :
cookie :
- $(openssl rand -base64 32)
cipher :
- $(openssl rand -base64 32)
Enable secure cookies
cookies :
domain : example.com
path : /
same_site : Lax # or Strict
secure : true # Required for HTTPS
Configure CORS properly
Only allow trusted origins: serve :
public :
cors :
enabled : true
allowed_origins :
- https://example.com
# Never use "*" in production ❌
allow_credentials : true
Use database SSL/TLS
Always encrypt database connections: dsn : postgres://user:pass@host/db?sslmode=require
# NOT sslmode=disable ❌
Protect admin API
Never expose the admin API publicly. Use:
Internal network only
API gateway with authentication
VPN access
IP allowlisting
Password security
Password policies
Enforce strong password requirements:
selfservice :
methods :
password :
enabled : true
config :
min_password_length : 12
identifier_similarity_check_enabled : true
haveibeenpwned_enabled : true
max_breaches : 0
Minimum password length. Recommended: 12 or higher.
Check passwords against HaveIBeenPwned breach database. Always enable in production.
identifier_similarity_check_enabled
Prevent passwords similar to email/username. Recommended: true.
Password hashing
Use Argon2id for new installations:
hashers :
algorithm : argon2
argon2 :
memory : 131072 # 128 MB
iterations : 3
parallelism : 4
salt_length : 16
key_length : 32
For bcrypt (legacy):
hashers :
algorithm : bcrypt
bcrypt :
cost : 12 # Minimum recommended
Session security
Session lifetime
Set appropriate session lifetimes:
session :
lifespan : 24h # Adjust based on security requirements
earliest_possible_extend : 1h
cookie :
persistent : true
same_site : Lax
Shorter session lifetimes improve security but reduce user convenience. Balance based on your risk profile.
Privileged sessions
Require re-authentication for sensitive operations:
selfservice :
flows :
settings :
privileged_session_max_age : 15m
required_aal : highest_available
Multi-factor authentication
Enforce MFA
Require MFA for all users or specific operations:
session :
whoami :
required_aal : aal2 # Require MFA
selfservice :
flows :
login :
after :
default_browser_return_url : https://example.com/
hooks :
- hook : require_verified_address
Available MFA methods
Enable multiple MFA options:
selfservice :
methods :
totp :
enabled : true
config :
issuer : MyApp
webauthn :
enabled : true
config :
rp :
display_name : MyApp
id : example.com
origins :
- https://example.com
passwordless : false # Use as second factor
lookup_secret :
enabled : true
Network security
Rate limiting
Kratos includes built-in rate limiting. Configure your reverse proxy for additional protection:
# Nginx example
http {
limit_req_zone $ binary_remote_addr zone=kratos_public:10m rate=10r/s;
limit_req_zone $ binary_remote_addr zone=kratos_admin:10m rate=5r/s;
server {
location / {
limit_req zone=kratos_public burst=20 nodelay;
proxy_pass http://kratos:4433;
}
}
}
DDoS protection
Use a CDN or DDoS protection service:
Cloudflare
AWS Shield
Google Cloud Armor
Firewall rules
iptables
AWS Security Group
# Allow only necessary ports
iptables -A INPUT -p tcp --dport 4433 -j ACCEPT # Public API
iptables -A INPUT -p tcp --dport 4434 -j DROP # Block admin from internet
resource "aws_security_group_rule" "kratos_public" {
type = "ingress"
from_port = 4433
to_port = 4433
protocol = "tcp"
cidr_blocks = [ "0.0.0.0/0" ]
security_group_id = aws_security_group . kratos . id
}
resource "aws_security_group_rule" "kratos_admin" {
type = "ingress"
from_port = 4434
to_port = 4434
protocol = "tcp"
cidr_blocks = [ "10.0.0.0/8" ] # Internal only
security_group_id = aws_security_group . kratos . id
}
Email security
SMTP security
Use encrypted SMTP connections:
SPF, DKIM, DMARC
Configure email authentication:
# SPF record
example.com. IN TXT "v=spf1 include:_spf.google.com ~all"
# DKIM record
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA..."
# DMARC record
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected] "
Secrets management
Never commit secrets to version control.
Use secret managers
Kubernetes Secrets
HashiCorp Vault
AWS Secrets Manager
apiVersion : v1
kind : Secret
metadata :
name : kratos-secrets
type : Opaque
stringData :
dsn : postgres://user:pass@host/db
secrets-cookie : your-32-char-secret
secrets-cipher : your-32-char-secret
# Store in Vault
vault kv put secret/kratos dsn="postgres://..."
# Retrieve in startup script
export DSN = $( vault kv get -field=dsn secret/kratos )
kratos serve -c config.yml
# Store in AWS
aws secretsmanager create-secret \
--name kratos/dsn \
--secret-string "postgres://..."
# Retrieve in application
DSN = $( aws secretsmanager get-secret-value \
--secret-id kratos/dsn \
--query SecretString \
--output text )
Secret rotation
Rotate secrets regularly:
secrets :
cookie :
- new-secret-here
- old-secret-for-rotation # Keep old secret temporarily
cipher :
- new-cipher-secret
- old-cipher-secret
Monitoring and alerts
Security events to monitor
Failed login attempts
Account lockouts
Password reset requests
Session anomalies
Admin API access
Database connection failures
Logging
Enable comprehensive logging:
log :
level : info
format : json
leak_sensitive_values : false # Never enable in production
Log aggregation
Send logs to a centralized system:
ELK Stack (Elasticsearch, Logstash, Kibana)
Splunk
Datadog
CloudWatch Logs
Compliance
GDPR compliance
Implement data export (Admin API)
Support account deletion
Maintain audit logs
Encrypt data at rest
Use data processing agreements
Data retention
# Example: Automated cleanup of old sessions
cron :
- schedule : "0 0 * * *"
command : |
DELETE FROM sessions
WHERE expires_at < NOW() - INTERVAL '90 days';
Configure your reverse proxy with security headers:
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Security updates
Subscribe to security advisories
Apply updates promptly
Test and deploy security patches quickly in production.
Next steps
CSRF protection Learn about CSRF token handling
Rate limiting Configure rate limits