Billionmail/BillionMail · critical
no default jwt secret found
Error message
no default jwt secret found
What it means
newJWTService builds the JWT signing secret from config, falling back to a default derived from the REDISPASS environment variable. If both the configured secret and the env-derived default are empty, it panics with 'no default jwt secret found' because issuing JWTs with an empty secret would be insecure. This is fail-fast startup behavior.
Source
Thrown at core/internal/service/rbac/jwt.go:48
Secret string // Secret key for signing JWT
AccessExpiry time.Duration // Duration for access token expiry
RefreshExpiry time.Duration // Duration for refresh token expiry
}
// newJWTService creates a new JWTService instance
func newJWTService() *JWTService {
defaultJwtSecret := ""
if dbpass, err := public.DockerEnv("DBPASS"); err == nil {
defaultJwtSecret += dbpass
}
if redispass, err := public.DockerEnv("REDISPASS"); err == nil {
defaultJwtSecret += redispass
}
if defaultJwtSecret == "" {
panic("no default jwt secret found")
}
return &JWTService{
Secret: g.Cfg().MustGet(context.Background(), "jwt.secret", defaultJwtSecret).String(),
AccessExpiry: time.Duration(g.Cfg().MustGet(context.Background(), "jwt.accessExpiry", 86400).Int()) * time.Second,
RefreshExpiry: time.Duration(g.Cfg().MustGet(context.Background(), "jwt.refreshExpiry", 86400*7).Int()) * time.Second,
}
}
// GenerateToken generates a new JWT token
func (s *JWTService) GenerateToken(accountId int64, username string, roles []string) (string, int64, error) {
expiryTime := time.Now().Add(s.AccessExpiry)
claims := &JWTCustomClaims{
AccountId: accountId,
Username: username,
Roles: roles,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiryTime),View on GitHub (pinned to fc36c76c05)
Solutions
- Set the REDISPASS environment variable (as docker-compose.yml does) before starting the service.
- Explicitly configure jwt.secret in the GoFrame config file.
- Run the service via docker compose so environment variables are injected.
- Check .env is loaded in non-Docker dev environments (source it or use a dotenv loader).
Example fix
// before export RUN_MODE=dev # REDISPASS missing ./billionmail // after export REDISPASS=$(grep REDISPASS .env | cut -d= -f2) ./billionmail
Defensive patterns
Strategy: validation
Validate before calling
// run before starting the app
if os.Getenv("REDISPASS") == "" {
cfgSecret := readConfig("jwt.secret")
if cfgSecret == "" {
log.Fatal("REDISPASS or jwt.secret must be set")
}
} Try / catch
func() {
defer func() {
if r := recover(); r != nil {
if strings.Contains(fmt.Sprint(r), "no default jwt secret found") {
log.Fatal("startup misconfiguration: set REDISPASS or jwt.secret")
}
panic(r)
}
}()
service.JWT()
}() Prevention
- Always deploy via docker compose (or ensure --env-file .env) so REDISPASS is present.
- Set an explicit jwt.secret in config for non-Docker environments.
- Add a startup preflight check that fails with a readable message before the panic.
- Never commit a default secret; keep .env out of VCS but documented in .env.example.
When it happens
Trigger: Starting the app without REDISPASS set in the environment and without jwt.secret in config — typically running the binary outside the Docker Compose environment.
Common situations: Local development without the .env file / docker compose env; REDISPASS renamed or removed in a custom deployment; config file missing the jwt.secret key.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- unexpected signing method: %v
- redis env init error: %v
- Logout failed: %w
- configuration value contains illegal characters
- empty token string
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/893d6c39e7cbf3c2.
Report an issue: GitHub.