Fix AWS CLI 'RequestExpired' Error Caused by System Clock Skew

beginnerโ˜๏ธ AWS2026-05-08| AWS CLI v2 on Linux (Ubuntu/CentOS/RHEL/Amazon Linux 2), macOS, Windows โ€” any AWS service using SigV4 signing (S3, EC2, IAM, etc.)

Error Message

RequestExpired: Request has expired. (Service: AmazonS3; Status Code: 403, Error Code: RequestExpired)
#aws-cli#time-sync#ntp#authentication#s3

TL;DR

Your system clock has drifted past AWS's 15-minute tolerance. AWS rejects any request signed with a timestamp that far off. One command usually fixes it:

# Linux (systemd)
sudo systemctl restart systemd-timesyncd
timedatectl status

# Or force sync with chrony
sudo chronyc makestep

# Amazon Linux 2 / EC2
sudo systemctl restart chronyd

If the clock is off by more than a few minutes, that's all you need.

Root Cause

AWS uses SigV4 (Signature Version 4) to authenticate API requests. Every request carries a timestamp, and AWS checks it against their servers. Drift more than ยฑ15 minutes in either direction and you get:

RequestExpired: Request has expired. (Service: AmazonS3; Status Code: 403, Error Code: RequestExpired)

It's a replay-attack defense โ€” old signed requests are worthless if the timestamp window is tight. Your credentials are fine. Your clock isn't.

Here's where this tends to catch people off guard:

  • A VM or container that was paused/suspended and resumed โ€” the clock freezes while paused
  • A server with NTP misconfigured or the NTP daemon crashed
  • A Docker container inheriting a drifted host clock
  • A laptop that slept for hours with NTP not catching up fast enough on wake
  • An EC2 instance where chronyd was accidentally stopped

Diagnose First

Run these before touching anything โ€” make sure the clock is actually the culprit:

# Check current system time vs. what AWS sees
date -u
curl -s --head https://s3.amazonaws.com | grep -i date

# Check NTP sync status (systemd)
timedatectl status

# Check chrony (Amazon Linux, RHEL, CentOS)
chronyc tracking

If timedatectl shows System clock synchronized: no or NTP service: inactive, that's your answer. An offset above ~500ms in chronyc tracking means it's actively drifting.

Quick numerical check against a known source:

# Compare local time vs. AWS time server
ntpdate -q time.aws.com 2>/dev/null || ntpdate -q pool.ntp.org

Fix: Sync the Clock

Option 1 โ€” systemd-timesyncd (Ubuntu/Debian)

# Enable and start NTP sync
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd

# Verify
timedatectl status
# Should show: System clock synchronized: yes

Option 2 โ€” chrony (Amazon Linux 2, RHEL, CentOS)

# Force immediate step (don't wait for gradual adjustment)
sudo chronyc makestep

# Restart if the daemon is down
sudo systemctl enable chronyd
sudo systemctl restart chronyd

# Verify
chronyc tracking
# Look for: System time offset < 1ms ideally

Option 3 โ€” ntpdate (quick one-shot sync)

# Install if missing
sudo apt install ntpdate   # Debian/Ubuntu
sudo yum install ntpdate   # RHEL/CentOS

# Sync immediately
sudo ntpdate -u pool.ntp.org
# or use AWS's own NTP server (169.254.169.123 on EC2)
sudo ntpdate -u 169.254.169.123

Heads up: ntpdate is a one-shot tool. It corrects the clock right now but doesn't keep it synced. For anything beyond a quick rescue, use chrony or systemd-timesyncd.

Option 4 โ€” macOS

# Enable network time
sudo sntp -sS time.apple.com

# Or via System Settings โ†’ General โ†’ Date & Time โ†’ "Set time automatically"

Option 5 โ€” Windows

# Run in elevated PowerShell
w32tm /resync /force
w32tm /query /status

Verify the Fix

Now test it. Run whichever command was failing, or use these:

# Test with a simple S3 list
aws s3 ls s3://your-bucket-name

# Or check your identity (works even without S3 access)
aws sts get-caller-identity

A real response instead of RequestExpired means you're good.

Special Case: Docker Containers

Containers share the host clock โ€” they have no NTP of their own. So if the host drifts by 20 minutes, every container on it drifts by 20 minutes. Fix the host, not the container:

# On the Docker host
sudo chronyc makestep

# Verify inside the container
docker exec your-container date -u

Running Docker on a VM (Docker Desktop on macOS or Windows)? The VM clock desyncs when the host machine sleeps. Restarting Docker Desktop usually resolves it โ€” it restarts the underlying VM and resets the clock.

EC2-Specific: Use the AWS Time Sync Service

EC2 instances have their own NTP endpoint at 169.254.169.123 โ€” link-local, no internet required, and more accurate than public NTP pools. Amazon Linux 2 points to it by default. Other AMIs often don't.

# Amazon Linux 2 โ€” already configured, just make sure chronyd is running
sudo systemctl status chronyd

# Ubuntu on EC2 โ€” add AWS time server to systemd-timesyncd
sudo nano /etc/systemd/timesyncd.conf
# Add or edit:
# [Time]
# NTP=169.254.169.123

sudo systemctl restart systemd-timesyncd
timedatectl show-timesync

Prevention

  • Keep a time sync daemon running at all times (chrony or systemd-timesyncd) โ€” disabling it to "save resources" will cost you more time debugging later
  • On EC2, use 169.254.169.123 as your primary NTP source โ€” it's free, sub-millisecond accurate, and doesn't need internet
  • In CI/CD pipelines with containers, add a preflight check that compares date -u against a known source before any AWS commands run
  • Using HashiCorp Vault or any token-based secrets system? Clock drift breaks those too, for the exact same reason

When debugging time-related auth issues on a locked-down server where you can't install packages, a quick way to check UTC offset is via a browser-based tool. ToolCraft has a no-upload, runs-entirely-in-browser option that's useful in exactly this situation.

Further Reading

  • AWS docs: Signing AWS API Requests โ€” explains SigV4 and the 15-minute window
  • Amazon EC2 User Guide: Setting the time for your Linux instance
  • chrony documentation: man chronyc, specifically the makestep directive

Related Error Notes