When a critical CVE drops at 3 AM, the clock starts ticking. The difference between a controlled six-hour patch cycle and a panicked weekend firefight comes down to process—not just speed.
I've watched teams scramble through uncoordinated Slack threads, skip staging, and push untested patches to production because "it's critical." That approach breaks more systems than it protects. A structured workflow lets you move fast without breaking things, and six hours is enough time to do it right if you know the steps.
Hour zero: Triage and impact assessment
The first thirty minutes decide everything else. You need three answers before touching a single server: which systems are affected, whether the vulnerability is actively exploited, and what breaking the patch might cost you.
Start with your asset inventory. If you don't have one, you're already behind—but you can build a quick list from your monitoring stack, configuration management database, or even SSH key distribution logs. For a typical LAMP stack vulnerability, check:
# Identify affected package versions across all hosts
ansible all -m shell -a "dpkg -l | grep libssl"
# or for RPM-based systems
ansible all -m shell -a "rpm -qa | grep openssl"
Cross-reference the CVE announcement against your running versions. Not every "critical" CVE affects your specific configuration—a container runtime vulnerability doesn't matter if you're running bare metal, and a privilege escalation bug loses severity if your services already run as root (though you should fix that too).
Check whether exploitation is theoretical or active. CISA's Known Exploited Vulnerabilities catalog and your intrusion detection logs tell you if attackers are already hunting for this. Active exploitation moves you to the fast track; theoretical risk gives you breathing room to test properly.
Document the blast radius in a shared runbook. Which customer-facing services depend on the affected component? What's the rollback path if the patch breaks something? I keep a simple table:
Affected: web-prod-*, api-gateway-2 through 8
Dependencies: PHP-FPM, MySQL connections
Rollback: yum downgrade openssl + service restart
Downtime window: 2-minute rolling restart per host
Hours one and two: Staging validation
Never apply a critical patch to production first. Not even if the vendor swears it's safe.
Your staging environment should mirror production's OS version, kernel, library stack, and workload. A Ubuntu 22.04 staging server doesn't validate a patch for Ubuntu 20.04 production—different glibc versions, different dependency trees, different breakage modes.
Pull the patch into staging and run your standard test suite, plus three additional checks:
Dependency conflicts. Package managers sometimes pull in unexpected upgrades alongside security fixes. Capture the full transaction:
# Dry-run the update and log everything it wants to change
apt-get update && apt-get install --simulate openssl > patch_plan.txt
Review that file for surprise kernel updates, Python version bumps, or anything that touches your application stack. I've seen a minor OpenSSL patch try to upgrade systemd because of transitive dependencies—that's a reboot you didn't plan for.
Service restart behavior. Some patches require a restart; some don't. Some claim they don't but actually do. Test it:
# Before patching: note running processes and their PIDs
ps aux | grep -E '(apache2|nginx|php-fpm|mysql)' > before.txt
# Apply patch
apt-get install --only-upgrade openssl
# After patching: compare
ps aux | grep -E '(apache2|nginx|php-fpm|mysql)' > after.txt
diff before.txt after.txt
If PIDs changed, the service restarted automatically. If they didn't but the old library is still mapped in memory, you need a manual restart to actually load the patched code:
lsof | grep 'DEL.*lib' | awk '{print $1}' | sort -u
That shows processes still using deleted (old) library files.
Application-level smoke tests. Run your critical user paths—login, checkout, API authentication, whatever breaks your SLA. Automated tests are great; manual clicks work too. If staging passes but you're still nervous, spin up an isolated canary instance and route 1% of production traffic to it for ten minutes. Watch error rates.
Hours three and four: Rollout strategy
Patching three hundred servers in parallel is how you turn a vulnerability fix into an outage. A phased rollout limits the blast radius and gives you time to catch problems before they spread.
Inventory segmentation. Group your hosts by risk and redundancy. Patch this order:
- Internal tools and dev environments (low user impact, high visibility into breakage)
- One production host behind a load balancer (canary)
- 10% of remaining production (still behind load balancers)
- Rest of production in batches of 25%
- Database primaries and single points of failure last, during a maintenance window if possible
For hosts behind a load balancer, drain connections before patching:
# HAProxy example
echo "disable server backend_web/web-prod-3" | socat stdio /var/run/haproxy.sock
# Wait for active connections to finish
watch 'netstat -tn | grep ESTABLISHED | wc -l'
# Patch and restart
apt-get install --only-upgrade openssl
systemctl restart apache2
# Re-enable
echo "enable server backend_web/web-prod-3" | socat stdio /var/run/haproxy.sock
Monitoring gates. After each batch, pause and check:
- HTTP 5xx rate (should stay flat)
- Application response time (p95 and p99)
- Service restart success (systemctl status across the batch)
- Disk I/O and CPU (sometimes a patch changes behavior under load)
Set a hard threshold: if error rate climbs 10% or response time doubles, stop the rollout and investigate. I once caught a PHP-FPM memory leak this way—the patch was fine, but it exposed a bug in our session handling that only surfaced under the new library version.
What if the patch breaks production?
You need a rollback plan before you start patching, not after something breaks.
Snapshot before patching. For VMs, take a filesystem snapshot. For bare metal, at minimum:
# Backup current package state
dpkg --get-selections > /root/package-state-before-patch.txt
apt-cache policy openssl > /root/openssl-version-before.txt
Downgrade procedure. Most distributions let you downgrade to the previous package version, but their repos don't always keep old versions. Before patching, download the current .deb or .rpm to a safe location:
# Debian/Ubuntu
apt-get download openssl
mv openssl_*.deb /root/rollback/
# RHEL/CentOS
yumdownloader openssl
mv openssl-*.rpm /root/rollback/
If the patch breaks things:
# Downgrade
apt-get install /root/rollback/openssl_*.deb
# or
yum downgrade /root/rollback/openssl-*.rpm
# Restart affected services
systemctl restart apache2 php7.4-fpm
# Verify
openssl version
Hold the package to prevent auto-updates while you investigate:
apt-mark hold openssl
# or
yum versionlock add openssl
Document the rollback in your runbook with timestamps. If you roll back, you're back to being vulnerable—so you need a secondary mitigation (firewall rule, WAF block, service isolation) while you figure out why the patch failed.
Hours five and six: Verification and documentation
The patch is deployed. Is the vulnerability actually closed?
Version confirmation. Check the patched version across all hosts:
ansible all -m shell -a "openssl version" | grep -A1 'web-prod'
Compare against the CVE announcement's fixed-in version. Sometimes vendors backport fixes to older version numbers, so "OpenSSL 1.1.1k" might contain the fix even though the CVE says "fixed in 1.1.1m"—check the distro's security advisory.
Vulnerability scanning. Run a targeted scan to confirm the CVE no longer appears:
# If you have Nessus, OpenVAS, or similar
nessuscli scan new --targets web-prod-1.example.com --policy "Critical CVE Check"
# Or a quick manual test if the CVE has a public PoC
curl -X POST https://web-prod-1.example.com/vulnerable-endpoint \
-H "Exploit-Header: payload" \
--max-time 5
No response or a patched error message means you're good. An unexpected crash or data leakage means the patch didn't take.
Incident timeline. Write down what happened, even if everything went smoothly:
- CVE announced: 03:17 UTC
- Triage completed: 03:45 UTC
- Staging validated: 05:30 UTC
- Production rollout started: 06:00 UTC
- Rollout completed: 08:45 UTC
- Vulnerability closed: 09:15 UTC
That timeline proves due diligence to auditors and helps you optimize next time. If staging took ninety minutes because you didn't have a snapshot ready, add snapshots to your pre-patch checklist.
Automation and the six-hour target
Can you do this faster with full automation? Sure—if you've already built the pipeline.
Configuration management tools like Ansible, Salt, or Puppet can apply patches across a fleet in minutes. But automation doesn't eliminate testing, and it doesn't make decisions for you. You still need:
- A staging validation step (automated tests + manual review)
- Phased rollout with monitoring gates
- A human approving each phase
I've seen fully automated patch systems push a bad update to every server in forty seconds. The rollback took six hours because nobody had tested the downgrade path.
If you don't have automation yet, six hours is still achievable with scripts and runbooks. Build the process first, then automate the repetitive parts.
Four questions I hear in every post-mortem
Why didn't we patch faster?
Because breaking production is worse than a three-hour exposure window. If you're under active attack, firewall rules and service isolation buy time while you validate the patch.
Can we skip staging for truly critical CVEs?
No. Test on a single non-production host at minimum. A five-minute staging check catches 80% of breaking changes.
What if the patch isn't available yet?
Mitigate at the perimeter: block exploit traffic with WAF rules, disable the vulnerable feature, isolate the affected service behind stricter firewall rules. Then wait for the vendor.
How do we know if we're still vulnerable after patching?
Run a vulnerability scan, check the running process memory maps for old libraries, and test the exploit PoC if one exists. Version numbers alone don't confirm remediation.
What to check first when the next CVE drops
Your runbook should start with these six lines:
- Which hosts run the affected software? (asset inventory)
- What version are we running? (package list or binary version check)
- Is exploitation active or theoretical? (CISA KEV + IDS logs)
- What breaks if we patch? (dependency check + staging test)
- What's the rollback path? (package backup + downgrade command)
- How do we verify remediation? (version check + vulnerability scan)
Answer those in the first thirty minutes and the rest of the six-hour clock is just execution. Keep the runbook updated with every patch cycle, and you'll spend less time figuring out what to do and more time doing it.
![Critical CVE Patching Workflow: 6-Hour Remediation [2026]](/images/blog/critical-cve-patching-workflow-6-hour-remediation-2026.jpg)