apereo/cas · warning
Ticket-granting ticket expiration policy is set to ALWAYS…
Error message
Ticket-granting ticket expiration policy is set to ALWAYS expire tickets.
What it means
The mirror case of error 405: when both primary TGT maxTimeToLiveInSeconds and timeToKillInSeconds are 'never durable' (e.g. 0), the builder warns that every TGT will ALWAYS expire and returns AlwaysExpiresExpirationPolicy.INSTANCE. Tickets granted under this policy are effectively unusable/immediately expired.
Solutions
- Set realistic positive values for cas.ticket.tgt.primary.max-time-to-live-in-seconds and time-to-kill-in-seconds.
- Check whether a placeholder/env variable (e.g. ${TGT_TTL}) resolves to 0 or empty and fix the value.
- Confirm you are editing the correct property namespace (cas.ticket.tgt.primary.*) and not the timeout/remember-me subsections.
- Restart and verify the logged policy name is TicketGrantingTicketExpirationPolicy, not AlwaysExpiresExpirationPolicy.
Example fix
// before
cas.ticket.tgt.primary.max-time-to-live-in-seconds=0
cas.ticket.tgt.primary.time-to-kill-in-seconds=0
// after
cas.ticket.tgt.primary.max-time-to-live-in-seconds=${TGT_MAX_TTL:28800}
cas.ticket.tgt.primary.time-to-kill-in-seconds=${TGT_TTK:14400} Defensive patterns
Strategy: validation
Validate before calling
long ttl = Long.parseLong(env.getProperty("cas.ticket.tgt.primary.max-time-to-live-in-seconds", "28800")); if (ttl <= 0) { fail("TGT max-time-to-live must be > 0"); } Prevention
- Verify env/placeholder values resolve to positive numbers before startup
- Watch startup logs for the ALWAYS/NEVER expire warnings and treat them as launch blockers
- Do not confuse milliseconds with seconds in cas.ticket.*.properties
When it happens
Trigger: Setting both cas.ticket.tgt.primary.max-time-to-live-in-seconds and time-to-kill-in-seconds to a 'never durable' value (e.g. 0) so toTicketGrantingTicketExpirationPolicy() builds AlwaysExpiresExpirationPolicy.
Common situations: Typo'd zeros in place of seconds; property placeholder resolving to empty/0; unit confusion (writing milliseconds where seconds are expected, e.g. 0 with intent 'instant').
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
- Primary ticket-granting ticket expiration policy is set to…
- 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/c8d9d6f43b3d33cf.
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:77
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();
val timeInBetweenSeconds = Beans.newDuration(tgt.getThrottledTimeout().getTimeInBetweenUsesInSeconds()).toSeconds();
policy.setTimeToKillInSeconds(seconds);
policy.setTimeInBetweenUsesInSeconds(timeInBetweenSeconds);
LOGGER.debug("Ticket-granting ticket expiration policy is based on throttled timeouts");
return policy;View on GitHub (pinned to e7288fc434)