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
chronydwas 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.123as 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 -uagainst 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 themakestepdirective

