Fail-Safe vs Fail-Secure
Learn the difference between fail-safe and fail-secure designs, when to use each, and how to apply them in your systems. Hands-on exercise included.
Focus: apply fail-safe and fail-secure designs
Picture this: you're on call at 2 a.m., and the authentication service that guards your entire customer database just went down. Your load balancer is configured to route traffic around unhealthy services, but it was designed to fail open — meaning when the auth service is unreachable, it lets requests through without checking credentials. Within minutes, attackers are walking straight through the front door you thought was locked. That's the moment you realize that the security posture of your system is often decided not by how it behaves under normal conditions, but by how it behaves when everything goes wrong. In this lesson, you'll master the two essential failure modes — fail-safe and fail-secure — and learn when to apply each one so that a crash doesn't become a catastrophe.
The problem this lesson solves
Systems fail. Disks fill up, networks partition, services crash, power blips. The default behavior in these moments is rarely a conscious design decision — it's whatever the developer happened to write first. But failures are exactly when attackers are most likely to strike, because they bet on your infrastructure not being designed for the worst case. If your service fails open, it grants access when it can't verify authorization. If it fails closed, it denies access when it can't verify. Both are valid patterns, but picking the wrong one for the wrong context turns a small outage into a security incident. This lesson gives you a mental framework for choosing deliberately, so your systems degrade safely instead of leaking data.
Core concept / mental model
Think of a physical building. A fail-safe door is like a fire exit: it swings open on power loss, letting people escape but also letting anyone in. A fail-secure door is like a vault: it stays locked on power loss, protecting contents but trapping people inside. Neither is always right — the fire exit is designed to save lives over protecting valuables; the vault protects valuables over saving lives. The same trade-off applies in software: fail-safe (also called fail-open) prioritizes availability; fail-secure (also called fail-closed) prioritizes security.
Pro tip: In security contexts, the term fail-secure is often synonymous with fail-closed, and fail-safe with fail-open. But be careful: fail-safe can also mean "the system becomes safe" — which in a security context might mean closed. Always clarify the terms in your organization.
The goal of both patterns is the same: when a component can't make a reliable decision, it should choose the outcome that prevents the worst outcome. If the worst outcome is data disclosure, fail closed. If the worst outcome is a life-threatening inaccessibility (e.g., medical emergency calls), fail open.
How it works step by step
- Identify the decision point: Where does your system make a trust decision? Example: API gateway verifying a JWT, database connection pool checking credentials, file access control.
- Determine the failure modes: What happens when that component errors, times out, or returns garbage? List both outcomes: allow (fail open) vs. deny (fail closed).
- Assess impact of each: For allow-on-error, what's the damage if an attacker can bypass? For deny-on-error, what's the damage if legitimate users are blocked? Consider confidentiality, integrity, and availability (the CIA triad).
- Choose the default: Based on impact, set the default behavior. Typically, security-critical decisions (auth, authorization, encryption) should fail closed. Availability-critical decisions (rate limiting, monitoring, caching) might fail open.
- Implement the fallback: In code, wrap error handling to explicitly return the chosen outcome — never let an unhandled exception accidentally default to allow.
- Test it: Kill the dependency in a test environment and verify the behavior. Include fault injection in your CI/CD pipeline.
Hands-on walkthrough
Let's apply this in Python. We'll build a simple token verifier that must fail closed when the verification service is unreachable.
import time
import random
def verify_token(token, auth_service):
"""Return True if token is valid, False otherwise. Fail closed on any error."""
try:
result = auth_service.check(token)
if result is None:
raise ValueError("Auth service returned no answer")
return result
except Exception:
# Any error -> deny access (fail closed)
return False
class FakeAuthService:
def check(self, token):
if random.random() < 0.3: # 30% chance of service crash
raise ConnectionError("Service unavailable")
return token == "valid_token"
service = FakeAuthService()
for _ in range(5):
token = "valid_token"
print(f"Token {token}: allowed? {verify_token(token, service)}")
Expected output (varies due to randomness): some runs return False when the service crashes, even for a valid token — that's fail-closed behavior.
Now, let's implement a fail-safe version for a non-critical feature, like a cache refresh.
def get_cached_or_live(cache, key):
"""Try cache first, then live DB. Fail open by returning stale data."""
try:
cached = cache.get(key)
if cached is not None:
return cached
except Exception:
pass # Cache unavailable - fall through to live DB
try:
live = db.fetch(key)
cache.set(key, live)
return live
except Exception:
return None # Could also raise, but fail-safe returns None
# Example: cache throws, db throws -> returns None (not a crash)
A more realistic example: a feature flag service with fail-open for non-security configuration.
class FeatureFlagClient:
def is_enabled(self, flag_name):
try:
return self._remote.fetch(flag_name)
except Exception:
return False # Fail-open: feature off, but no crash
# Usage
if client.is_enabled("new_checkout"):
checkout = new_checkout
else:
checkout = old_checkout
Compare options / when to choose what
| Pattern | Also called | Priority | Good for | Bad for |
|---|---|---|---|---|
| Fail-open | Fail-safe | Availability | Non-critical features, rate limiting, UI fallbacks | Authentication, authorization, encryption |
| Fail-closed | Fail-secure | Security | AuthN, AuthZ, payment processing, data encryption | Systems where availability is life-critical (e.g., emergency services) |
When to choose what: - Fail closed when the consequence of unauthorized access is high (data breach, fraud, privilege escalation). - Fail open when the consequence of blocking legitimate users is worse than the risk of unauthorized access (e.g., healthcare emergency hotlines, public safety alerts). - Hybrid approach: fail open for read-only operations, fail closed for write operations. Example: an e-commerce website might allow browsing (fail-open cache) but require auth for checkout (fail-closed).
Troubleshooting & edge cases
- Problem: Your service fails open because an unhandled exception propagates and the default
try/exceptisn't catching it. Fix: Add a broadexcept Exceptionat the boundary of your trust decision, and explicitlyreturn False(orraise PermissionError). Never rely on implicit behavior. - Problem: You set fail-closed, but your health checks also fail when the service is down, so the load balancer marks the instance as unhealthy and routes to another — which also fails closed. You get a cascading outage. Fix: Separate health check from security check. Health checks should not require authentication to avoid a self-inflicted denial of service.
- Edge case: A distributed system where some nodes have the auth service and others don't. A node without the auth service might fail closed, but if the node is supposed to be a read replica, maybe fail open is appropriate. Fix: Configure behavior based on role and consistency requirements.
- Edge case: Timeouts. A timeout can be intermittent, and fail-closed may cause frequent denials. Use appropriate timeouts and circuit breakers to avoid false negatives.
- Gotcha: Your code checks
if result:but the auth service returnsTrue/Falseonly; if it returnsNoneon error,if result:treats it as falsy and fails closed — that's okay, but be explicit to make it readable.
What you learned & what's next
You've learned the critical distinction between fail-safe (fail-open) and fail-secure (fail-closed) designs. You understand that failures are inevitable, and the security of your systems hinges on your deliberate choice of default behavior. You've seen hands-on Python examples of both patterns, and you know how to assess which to use for a given context. You've also learned to watch for common pitfalls like cascading failures and timeout issues.
This principle ties directly into the next lesson in the Security foundations track, where we'll explore defense in depth — layering multiple controls to ensure that even if one fails, another catches it. The fail-safe/fail-secure choice is a key layer in that strategy, and you'll see how it interacts with other security mechanisms.
Next step: Review your existing codebase for trust decisions and identify whether each fails open or closed by default. Practice rewriting one function to fail closed explicitly in a small project — you'll be surprised how often the default is unsafe.
Practice recap
Take a small Python service you've written and identify one trust decision (e.g., user authentication, API key validation). Rewrite it to fail closed explicitly with a try/except block. Then simulate a failure by raising an exception inside the dependency and verify that the function returns False or raises a security exception. Next, review a non-critical function that uses a cache and convert it to fail-open, and test it by making the cache throw an exception. This hands-on exercise will cement the difference between the two patterns.
Common mistakes
- Assuming that fail-safe and fail-secure are the same — they are opposite: fail-safe often means fail-open (availability), fail-secure means fail-closed (security).
- Using fail-open for authentication or authorization because it's more convenient for users — this is a classic vulnerability.
- Catching exceptions in a broad handler that accidentally swallows errors and returns True (allow) — always explicitly choose the outcome.
- Not testing failure modes — only testing happy path leaves your fail behavior unverified.
- Applying the same policy globally without considering the context (e.g., read vs. write operations).
Variations
- Fail-open vs. fail-closed are not the only options; you can also use fail-skewed (e.g., return a limited view) or circuit breakers that fail open after a threshold but fail closed initially.
- In microservices, use an API gateway to centralize fail decisions, rather than implementing them individually in each service.
- In Python, you can implement fail-secure using Python's
contextlibto create a context manager that always denies on exit if checkout fails.
Real-world use cases
- An e-commerce platform's payment service: fail closed when payment verification fails to prevent fraudulent transactions; fail open when the product catalog API is down to keep the store browsing accessible.
- A smart building's access control system: fail open on fire alarm to let people escape (life safety), but fail closed on a suspected intrusion to prevent entry.
- A microservice architecture where rate-limiting service fails open to avoid blocking legitimate traffic, but the authentication service fails closed during a network partition.
Key takeaways
- Fail-safe and fail-secure are two distinct design patterns: fail-safe prioritizes availability, fail-secure prioritizes confidentiality/integrity.
- For security-critical decisions like authentication and authorization, always fail closed.
- For availability-critical functions like caching and rate limiting, failing open is often acceptable.
- Never rely on implicit defaults; explicitly code the failure behavior.
- Test failure modes using fault injection to ensure your system behaves as intended.
- Your choice of fail mode directly impacts your overall security posture.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.