apereo/cas · warning
Primary ticket-granting ticket expiration policy is set to…
Error message
Primary ticket-granting ticket expiration policy is set to NEVER expire tickets.
What it means
When building the TGT expiration policy from cas.ticket.tgt.* properties, TicketGrantingTicketExpirationPolicyBuilder checks Beans.isInfinitelyDurable on both primary maxTimeToLiveInSeconds and timeToKillInSeconds. If both are effectively infinite (e.g. PT-forever sentinel values), it warns that tickets will NEVER expire and returns NeverExpiresExpirationPolicy.INSTANCE. This is a deliberate configuration warning about a security-sensitive setup.
Solutions
- Set finite values for cas.ticket.tgt.primary.max-time-to-live-in-seconds and time-to-kill-in-seconds (e.g. 28800 / 3600).
- If never-expiring TGTs are truly intended, accept the warning and ensure explicit cleanup (eviction/TTL at the ticket registry level, e.g. Redis/TTL sweeps) exists.
- Verify you did not accidentally use the 'forever' sentinel meant for other settings.
- Review remember-me vs primary settings; use toRememberMeTicketExpirationPolicy semantics rather than infinite primary TGTs.
Example fix
// before cas.ticket.tgt.primary.max-time-to-live-in-seconds=-1 cas.ticket.tgt.primary.time-to-kill-in-seconds=-1 // after cas.ticket.tgt.primary.max-time-to-live-in-seconds=28800 cas.ticket.tgt.primary.time-to-kill-in-seconds=14400
Defensive patterns
Strategy: validation
Validate before calling
if (cas.ticket.tgt.primary.maxTimeToLiveInSeconds <= 0 || cas.ticket.tgt.primary.timeToKillInSeconds <= 0) { fail("TGT expiration values must be positive finite seconds"); } Prevention
- Never ship -1/forever sentinel values for TGT TTLs to production
- Add config review checks that assert finite positive TGT durations
- If infinite TGTs are intentional, compensate with registry-level eviction/TTL
When it happens
Trigger: Setting both cas.ticket.tgt.primary.max-time-to-live-in-seconds and cas.ticket.tgt.primary.time-to-kill-in-seconds to an 'infinite' value (Beans.isInfinitelyDurable, e.g. -1 or the forever sentinel) so toTicketGrantingTicketExpirationPolicy() builds NeverExpiresExpirationPolicy.
Common situations: Copy-pasted 'make sessions last forever' configuration; sample configs with -1 values left in production; operators surprised that logout alone no longer cleans stale TGTs, leading to unbounded ticket-registry growth.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Ticket-granting ticket expiration policy is set to ALWAYS…
- Dn format cannot be empty/blank for authentication
- MultifactorAuthenticationProviderAbsentException
- No expiration policy was found for ticket state
- No expiration policy was found for ticket state
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/7f0cc8950d48f4d0.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/expiration/builder/TicketGrantingTicketExpirationPolicyBuilder.java:72
* @return the expiration policy
*/
public ExpirationPolicy toRememberMeTicketExpirationPolicy() {
val tgt = casProperties.getTicket().getTgt();
val timeToKillInSeconds = Beans.newDuration(tgt.getRememberMe().getTimeToKillInSeconds()).toSeconds();
LOGGER.debug("Remember me expiration policy is being configured based on hard timeout of [{}] seconds", timeToKillInSeconds);
val rememberMePolicy = new HardTimeoutExpirationPolicy(timeToKillInSeconds);
val policy = new RememberMeDelegatingExpirationPolicy();
policy.addPolicy(RememberMeDelegatingExpirationPolicy.POLICY_NAME_REMEMBER_ME, rememberMePolicy);
policy.addPolicy(BaseDelegatingExpirationPolicy.POLICY_NAME_DEFAULT, toTicketGrantingTicketExpirationPolicy());
return policy;
}
private ExpirationPolicy toTicketGrantingTicketExpirationPolicy() {
val tgt = casProperties.getTicket().getTgt();
if (Beans.isInfinitelyDurable(tgt.getPrimary().getMaxTimeToLiveInSeconds())
&& Beans.isInfinitelyDurable(tgt.getPrimary().getTimeToKillInSeconds())) {
LOGGER.warn("Primary ticket-granting ticket expiration policy is set to NEVER expire tickets.");
return NeverExpiresExpirationPolicy.INSTANCE;
}
if (Beans.isNeverDurable(tgt.getPrimary().getMaxTimeToLiveInSeconds())
&& Beans.isNeverDurable(tgt.getPrimary().getTimeToKillInSeconds())) {
LOGGER.warn("Ticket-granting ticket expiration policy is set to ALWAYS expire tickets.");
return AlwaysExpiresExpirationPolicy.INSTANCE;
}
if (StringUtils.isNotBlank(tgt.getTimeout().getMaxTimeToLiveInSeconds())) {
val seconds = Beans.newDuration(tgt.getTimeout().getMaxTimeToLiveInSeconds()).toSeconds();
LOGGER.debug("Ticket-granting ticket expiration policy is based on a timeout of [{}] seconds", seconds);
return new TimeoutExpirationPolicy(seconds);
}
if (StringUtils.isNotBlank(tgt.getThrottledTimeout().getTimeInBetweenUsesInSeconds())
&& StringUtils.isNotBlank(tgt.getThrottledTimeout().getTimeToKillInSeconds())) {
val policy = new ThrottledUseAndTimeoutExpirationPolicy();
val seconds = Beans.newDuration(tgt.getThrottledTimeout().getTimeToKillInSeconds()).toSeconds();View on GitHub (pinned to e7288fc434)