You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.8 KiB

Fix: Honor --days flag for short-lived certificates

Issue Description

The --days flag in acme.sh is currently ignored for short-lived certificates (typically 5-50 days validity), causing unexpected renewal behavior that differs from user expectations and normal certificate handling.

Problem Details

Current Behavior:

  • For normal certificates (90+ days): --days 60 works as expected, scheduling renewal ~60 days after issuance
  • For short-lived certificates (5-50 days): --days value is completely ignored, renewal is automatically set to 1 day before expiration regardless of user preference

Root Cause: The renewal calculation logic has two separate code paths:

  1. When _notAfter is NOT set (normal certs): Uses Le_RenewalDays from --days flag
  2. When _notAfter IS set (short-lived certs): Ignores Le_RenewalDays and uses hardcoded fallback logic

This inconsistency exists in the certificate processing logic around lines 5388-5411, where short-lived certificates follow a different renewal calculation that doesn't consider the user's --days setting.

Impact

This affects users working with:

  • Short-lived certificates from CAs like Sectigo InCommon, DigiCert, etc.
  • Testing environments with brief certificate lifespans
  • High-security environments requiring frequent certificate rotation
  • Upcoming industry standards (200 days by 2026, 100 days by 2027, 47 days by 2029)

Example Issue:

./acme.sh --issue -d example.com --valid-to "+20d" --days 7
# Expected: Renew 7 days after issuance
# Actual: Renews 1 day before expiration (19 days after issuance), ignoring --days 7

Solution

Changes Made

1. Enhanced Renewal Logic for Short-lived Certificates

  • Modified the _notAfter code path to respect user's --days setting first
  • Added safety check to prevent renewal after certificate expiration
  • Maintained fallback logic for cases where user's setting is invalid

2. Added Parameter Validation

  • Early validation for --days parameter (1-398 days range)
  • Aligned maximum with current industry standard (398 days)
  • Clear error messages for invalid values

3. Improved User Feedback

  • Added informational messages explaining which renewal logic is being used
  • Warning messages when fallback logic is triggered
  • Clear indication when user's setting conflicts with certificate validity

Technical Implementation

Core Logic Change (lines ~5388-5427):

if [ "$_notAfter" ]; then
  Le_CertExpireTime=$(_date2time "$_notAfter")
  
  # NEW: Calculate renewal time based on user's --days setting first
  Le_UserRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
  
  # NEW: Check if user's renewal time is after certificate expiration
  if [ "$Le_UserRenewTime" -ge "$Le_CertExpireTime" ]; then
    # Fallback to safe defaults with clear messaging
    # (existing logic for 1 day/1 hour before expiration)
  else
    # NEW: Use user's setting when valid
    Le_NextRenewTime="$Le_UserRenewTime"
    _info "Using user-specified renewal time: $Le_RenewalDays days after issuance"
  fi
fi

Validation Enhancement (lines ~5351-5364):

# NEW: Reasonable validation for --days parameter
if [ "$Le_RenewalDays" -gt "398" ]; then
  _err "Invalid --days value: $Le_RenewalDays. Maximum supported is 398 days (current industry standard)."
  return 1
fi
if [ "$Le_RenewalDays" -eq "0" ]; then
  _err "Invalid --days value: $Le_RenewalDays. Minimum supported is 1 day."
  return 1
fi

Logic Flow

  1. Early Validation: Check --days parameter for reasonable range (1-398 days)
  2. User Preference: Calculate renewal time based on user's --days setting
  3. Safety Check: Verify user's setting doesn't cause renewal after expiration
  4. Fallback Logic: Use safe defaults only when user's setting is invalid
  5. Clear Feedback: Inform user which logic was applied and why

Behavior Examples

Valid User Settings (respected):

--days 7 with 20-day cert → Renews 7 days after issuance ✓
--days 15 with 30-day cert → Renews 15 days after issuance ✓
--days 30 with 90-day cert → Renews 30 days after issuance ✓

Invalid User Settings (fallback with warning):

--days 25 with 20-day cert → Warning + fallback to 1 day before expiration
--days 0 → Error: "Minimum supported is 1 day"
--days 500 → Error: "Maximum supported is 398 days"

Backward Compatibility

  • Existing behavior preserved when no --days flag is used
  • Normal certificates maintain existing renewal logic (with 1-day safety buffer)
  • Short-lived certificates without --days continue using current fallback logic
  • No breaking changes to configuration file format or cron behavior

Future-Proofing

This fix prepares acme.sh for the industry's transition to shorter certificate lifespans:

  • Current: 398 days maximum
  • March 2026: 200 days maximum
  • March 2027: 100 days maximum
  • March 2029: 47 days maximum

The validation and logic gracefully handle these transitions while respecting user intent.

Testing

Test Case 1: Short-lived certificate with valid --days

./acme.sh --issue -d test.example.com --valid-to "+20d" --days 7
Result: Le_RenewalDays='7', renewal scheduled exactly 7 days after issuance ✓

Test Case 2: Short-lived certificate with invalid --days

./acme.sh --issue -d test.example.com --valid-to "+15d" --days 20
Result: Warning message + fallback to 1 day before expiration ✓

Test Case 3: Normal certificate behavior unchanged

./acme.sh --issue -d test.example.com --days 60  
Result: Renewal scheduled 59 days after issuance (existing behavior)

This fix ensures the --days flag works consistently across all certificate types while maintaining safety and providing clear user feedback.