Loading content...
The most dangerous configuration errors are those that go undetected at startup and manifest as mysterious runtime failures hours or days later. A typo in a database hostname, an invalid port number, a missing required field—these errors should halt application startup immediately, not cause cryptic errors at 3 AM when the first user hits that code path.
Configuration validation is the practice of verifying that configuration is complete, correct, and consistent before the application begins serving traffic. It embodies the fail-fast principle: if something is wrong, fail immediately and loudly rather than continuing in a compromised state.
This page explores multi-layered validation strategies that catch configuration errors at the earliest possible moment—when fixing them is cheap and the blast radius is zero.
By the end of this page, you will understand how to implement comprehensive configuration validation using schema validation, semantic rules, dependency checks, and connection validation. You'll learn to design startup sequences that fail fast with actionable error messages.
Effective configuration validation operates at multiple layers, with each layer catching different categories of errors. Think of it as defense in depth—even if one layer misses an error, subsequent layers have opportunities to catch it.
The validation pyramid:
| Layer | What It Validates | When It Runs | Examples |
|---|---|---|---|
| Structure, types, required fields | Configuration loading | Port is a number, host is a string |
| Value constraints, patterns | After schema validation | Port in 1-65535, URL is valid format |
| Logical consistency, rules | After range validation | Max > min, timeout < ttl |
| Related configuration consistency | After semantic validation | If SSL enabled, cert path must exist |
| External resources are reachable | Application startup | Database connects, API responds |
Why layered validation matters:
Each layer provides increasingly specific feedback. A schema error tells you 'port must be a number.' A range error tells you 'port must be 1-65535.' A connectivity error tells you 'cannot connect to database at db.production.internal:5432.' This progression from general to specific makes debugging configuration issues straightforward.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
interface ValidationLayer { name: string; validate(config: unknown): ValidationResult;} class ConfigurationValidator { private layers: ValidationLayer[] = []; addLayer(layer: ValidationLayer): this { this.layers.push(layer); return this; } async validate(rawConfig: unknown): Promise<ValidationResult> { const errors: ValidationError[] = []; const warnings: ValidationWarning[] = []; let config = rawConfig; for (const layer of this.layers) { console.log(`Running validation layer: ${layer.name}`); const result = await layer.validate(config); errors.push(...result.errors); warnings.push(...result.warnings); // Stop on critical errors - subsequent layers need valid input if (result.errors.some(e => e.severity === "critical")) { break; } // Layer may transform/coerce config for next layer if (result.transformedConfig) { config = result.transformedConfig; } } return { valid: errors.length === 0, errors, warnings, validatedConfig: errors.length === 0 ? config as AppConfig : undefined, }; }} // Usageconst validator = new ConfigurationValidator() .addLayer(new SchemaValidationLayer(appConfigSchema)) .addLayer(new RangeValidationLayer(rangeRules)) .addLayer(new SemanticValidationLayer(semanticRules)) .addLayer(new DependencyValidationLayer(dependencyRules)) .addLayer(new ConnectivityValidationLayer()); const result = await validator.validate(loadedConfig); if (!result.valid) { console.error("Configuration validation failed:"); for (const error of result.errors) { console.error(` [${error.layer}] ${error.path}: ${error.message}`); } process.exit(1);}Schema validation verifies that configuration has the expected structure—correct property names, appropriate types, and required fields present. This is the first line of defense against typos and malformed configuration.
Schema validation with Zod (recommended):
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
import { z } from "zod"; // Define schema with Zod - provides validation AND TypeScript typesconst DatabaseConfigSchema = z.object({ host: z.string().min(1, "Host cannot be empty"), port: z.number().int("Port must be an integer"), database: z.string().min(1, "Database name required"), username: z.string().min(1, "Username required"), password: z.string().min(1, "Password required"), poolSize: z.number().int().positive().default(10), connectionTimeoutMs: z.number().positive().default(30000), queryTimeoutMs: z.number().positive().default(60000), useSsl: z.boolean().default(true), sslCertPath: z.string().optional(),}); const ServerConfigSchema = z.object({ http: z.object({ port: z.number().int().min(1).max(65535), host: z.string().default("0.0.0.0"), requestTimeoutMs: z.number().positive().default(60000), }), tls: z.object({ enabled: z.boolean(), certPath: z.string().optional(), keyPath: z.string().optional(), }).optional(),}); const AppConfigSchema = z.object({ name: z.string(), version: z.string(), environment: z.enum(["development", "test", "staging", "production"]), database: DatabaseConfigSchema, server: ServerConfigSchema, logging: LoggingConfigSchema, features: FeatureFlagsSchema,}); // Infer TypeScript type from schematype AppConfig = z.infer<typeof AppConfigSchema>; // Schema validation functionfunction validateSchema(rawConfig: unknown): AppConfig { const result = AppConfigSchema.safeParse(rawConfig); if (!result.success) { const errors = result.error.issues.map(issue => ({ path: issue.path.join("."), message: issue.message, code: issue.code, })); throw new SchemaValidationError( "Configuration schema validation failed", errors ); } return result.data;} // Example usage and error outputtry { const config = validateSchema({ name: "myapp", // missing version environment: "prod", // Invalid enum value database: { host: "", // Empty string port: "5432", // String instead of number // missing required fields }, });} catch (error) { // Clear error messages: // - environment: Invalid enum value. Expected 'development' | 'test' | 'staging' | 'production', received 'prod' // - database.host: Host cannot be empty // - database.port: Expected number, received string // - database.database: Required // - database.username: Required // - database.password: Required}Define your configuration schema first, then derive TypeScript types from it using z.infer<typeof Schema>. This ensures your types and validation rules are always in sync—the schema is the single source of truth.
After schema validation ensures structural correctness, range and format validation verifies that values are within acceptable bounds and match expected patterns.
Common range and format rules:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
import { z } from "zod"; // Port validation with refined constraintsconst PortSchema = z.number() .int("Port must be an integer") .min(1, "Port must be at least 1") .max(65535, "Port must be at most 65535"); // URL validationconst HttpUrlSchema = z.string() .url("Must be a valid URL") .refine( (url) => url.startsWith("http://") || url.startsWith("https://"), "URL must use HTTP or HTTPS protocol" ); // Duration validation (with unit clarity)const DurationMsSchema = z.number() .positive("Duration must be positive") .max(300000, "Duration cannot exceed 5 minutes"); // Email formatconst EmailSchema = z.string().email("Invalid email format"); // Hostname validation (DNS name or IP)const HostnameSchema = z.string().refine( (host) => { // Simple DNS or IP pattern const dnsPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/; return dnsPattern.test(host) || ipv4Pattern.test(host); }, "Must be a valid hostname or IP address"); // Cron expression validationconst CronExpressionSchema = z.string().refine( (cron) => { // Basic cron pattern validation const parts = cron.split(" "); return parts.length === 5 || parts.length === 6; }, "Must be a valid cron expression (5 or 6 parts)"); // File path that must exist (validated at runtime)const ExistingFilePathSchema = z.string().refine( (path) => fs.existsSync(path), (path) => ({ message: `File does not exist: ${path}` })); // Percentage (0-100)const PercentageSchema = z.number() .min(0, "Percentage cannot be negative") .max(100, "Percentage cannot exceed 100"); // Memory size with unitsconst MemorySizeSchema = z.string().refine( (size) => /^\d+(B|KB|MB|GB)$/i.test(size), "Memory size must be in format: 512MB, 1GB, etc."); // Log level enumconst LogLevelSchema = z.enum( ["trace", "debug", "info", "warn", "error", "fatal"], { errorMap: () => ({ message: "Invalid log level" }) });Custom validation messages:
Validation messages should be actionable—they should tell the operator exactly what's wrong and hint at how to fix it:
123456789101112131415161718192021222324252627282930
// ❌ BAD: Vague error messagesconst BadPortSchema = z.number().positive();// Error: "Number must be greater than 0" // ✅ GOOD: Specific, actionable error messagesconst GoodPortSchema = z.number({ required_error: "Server port is required (e.g., APP_SERVER_PORT=8080)", invalid_type_error: "Server port must be a number, got string. Check APP_SERVER_PORT",}) .int("Server port must be an integer (not 8080.5)") .min(1, "Server port must be at least 1") .max(65535, "Server port must be at most 65535 (got {value})") .refine( (port) => port >= 1024 || process.getuid() === 0, "Ports below 1024 require root privileges. Use a port >= 1024 or run as root." ); // ✅ GOOD: Include environment variable hintfunction createEnvHint(envVar: string): string { return `Set via environment variable: ${envVar}`;} const DatabaseHostSchema = z.string({ required_error: `Database host is required. ${createEnvHint("APP_DATABASE_HOST")}`,}) .min(1, "Database host cannot be empty") .refine( (host) => !host.includes(" "), "Database host cannot contain spaces" );Semantic validation checks logical consistency—rules that depend on relationships between multiple configuration values rather than individual value constraints.
Common semantic validation rules:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
interface SemanticRule { name: string; validate: (config: AppConfig) => SemanticValidationResult;} const semanticRules: SemanticRule[] = [ // Min/Max consistency { name: "Pool size range", validate: (config) => { const { minConnections, maxConnections } = config.database.pool; if (minConnections > maxConnections) { return { valid: false, error: `minConnections (${minConnections}) cannot exceed maxConnections (${maxConnections})`, suggestion: "Either increase maxConnections or decrease minConnections", }; } return { valid: true }; }, }, // Timeout hierarchy { name: "Timeout consistency", validate: (config) => { const { connectionTimeoutMs, queryTimeoutMs } = config.database; if (connectionTimeoutMs >= queryTimeoutMs) { return { valid: false, error: `connectionTimeout (${connectionTimeoutMs}ms) should be less than queryTimeout (${queryTimeoutMs}ms)`, suggestion: "Connection should fail faster than queries to avoid blocking connections", }; } return { valid: true }; }, }, // Cache TTL vs refresh interval { name: "Cache timing consistency", validate: (config) => { if (!config.cache.enabled) return { valid: true }; const { ttlSeconds, refreshIntervalSeconds } = config.cache; if (refreshIntervalSeconds && refreshIntervalSeconds >= ttlSeconds) { return { valid: false, error: `refreshInterval (${refreshIntervalSeconds}s) should be less than TTL (${ttlSeconds}s)`, suggestion: "Refresh should occur before cache entries expire", }; } return { valid: true }; }, }, // Retry timing { name: "Retry exponential backoff", validate: (config) => { const { retryCount, retryDelayMs, maxRetryDelayMs } = config.http; const finalDelay = retryDelayMs * Math.pow(2, retryCount - 1); if (finalDelay > maxRetryDelayMs) { return { valid: false, error: `Exponential backoff would exceed maxRetryDelay on attempt ${retryCount}`, suggestion: `Either reduce retryCount or increase maxRetryDelayMs to at least ${finalDelay}`, }; } return { valid: true }; }, }, // Resource limits vs expected load { name: "Pool size for expected load", validate: (config) => { if (config.environment !== "production") return { valid: true }; const { maxConnections } = config.database.pool; const { maxConcurrentRequests } = config.server.http; if (maxConnections < maxConcurrentRequests / 10) { return { valid: false, warning: `Database pool size (${maxConnections}) may be too small for ${maxConcurrentRequests} concurrent requests`, suggestion: "Consider increasing database.pool.maxConnections", }; } return { valid: true }; }, }, // Environment-specific rules { name: "Production security requirements", validate: (config) => { if (config.environment !== "production") return { valid: true }; const errors: string[] = []; if (!config.server.tls?.enabled) { errors.push("TLS must be enabled in production"); } if (config.logging.level === "debug" || config.logging.level === "trace") { errors.push("Debug/trace logging not allowed in production"); } if (!config.database.useSsl) { errors.push("Database SSL must be enabled in production"); } return { valid: errors.length === 0, errors, }; }, },]; function validateSemantic(config: AppConfig): SemanticValidationResult { const allErrors: string[] = []; const allWarnings: string[] = []; const suggestions: string[] = []; for (const rule of semanticRules) { const result = rule.validate(config); if (!result.valid && result.error) { allErrors.push(`[${rule.name}] ${result.error}`); } if (result.errors) { allErrors.push(...result.errors.map(e => `[${rule.name}] ${e}`)); } if (result.warning) { allWarnings.push(`[${rule.name}] ${result.warning}`); } if (result.suggestion) { suggestions.push(result.suggestion); } } return { valid: allErrors.length === 0, errors: allErrors, warnings: allWarnings, suggestions };}Use errors for configurations that will definitely cause problems. Use warnings for configurations that are suspicious but might work—like a small connection pool that might be intentional. Errors block startup; warnings are logged but allowed.
Dependency validation checks that when one configuration option is set, its dependencies are also properly configured. This catches the common error of enabling a feature without providing its required settings.
Dependency patterns:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
// Using Zod's refine for dependent validationconst TlsConfigSchema = z.object({ enabled: z.boolean(), certPath: z.string().optional(), keyPath: z.string().optional(), minVersion: z.enum(["TLS1.2", "TLS1.3"]).optional(),}).refine( (tls) => { // If TLS is enabled, cert and key paths are required if (tls.enabled) { return tls.certPath && tls.keyPath; } return true; }, { message: "When TLS is enabled, both certPath and keyPath are required", path: ["certPath", "keyPath"], }); const CacheConfigSchema = z.object({ enabled: z.boolean(), backend: z.enum(["memory", "redis"]).optional(), redisHost: z.string().optional(), redisPort: z.number().optional(), ttlSeconds: z.number().positive().optional(),}).refine( (cache) => { // If cache is enabled with Redis backend, Redis config required if (cache.enabled && cache.backend === "redis") { return cache.redisHost && cache.redisPort; } return true; }, { message: "Redis cache requires redisHost and redisPort", path: ["redisHost"], }).refine( (cache) => { // If cache is enabled, TTL is required if (cache.enabled) { return cache.ttlSeconds !== undefined; } return true; }, { message: "Enabled cache requires ttlSeconds", path: ["ttlSeconds"], }); // More complex dependency validationinterface DependencyRule { name: string; condition: (config: AppConfig) => boolean; requirements: (config: AppConfig) => DependencyCheck[];} interface DependencyCheck { description: string; satisfied: boolean; fix?: string;} const dependencyRules: DependencyRule[] = [ { name: "SSL Certificate Files", condition: (config) => config.server.tls?.enabled === true, requirements: (config) => [ { description: "TLS certificate file exists", satisfied: fs.existsSync(config.server.tls!.certPath!), fix: `Create or provide certificate at: ${config.server.tls!.certPath}`, }, { description: "TLS key file exists", satisfied: fs.existsSync(config.server.tls!.keyPath!), fix: `Create or provide key file at: ${config.server.tls!.keyPath}`, }, { description: "Certificate is readable", satisfied: canReadFile(config.server.tls!.certPath!), fix: "Check file permissions on certificate", }, ], }, { name: "Monitoring Dependencies", condition: (config) => config.monitoring.enabled, requirements: (config) => [ { description: "Metrics endpoint configured", satisfied: !!config.monitoring.metricsEndpoint, fix: "Set monitoring.metricsEndpoint", }, { description: "Service name for metrics tagging", satisfied: !!config.name, fix: "Set application name for metrics identification", }, ], }, { name: "Database SSL Configuration", condition: (config) => config.database.useSsl === true, requirements: (config) => [ { description: "SSL CA certificate path provided", satisfied: !!config.database.sslCaPath, fix: "Set database.sslCaPath for SSL verification", }, ], },]; function validateDependencies(config: AppConfig): DependencyValidationResult { const errors: string[] = []; for (const rule of dependencyRules) { if (!rule.condition(config)) continue; const requirements = rule.requirements(config); for (const req of requirements) { if (!req.satisfied) { errors.push(`[${rule.name}] ${req.description} - ${req.fix || "Check configuration"}`); } } } return { valid: errors.length === 0, errors };}The ultimate validation is testing that configured resources are actually reachable. Connectivity validation catches deployment issues where configuration is syntactically correct but points to unreachable services.
Connectivity validation patterns:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
interface ConnectivityCheck { name: string; check: (config: AppConfig) => Promise<ConnectivityResult>; required: boolean; // Block startup if fails? timeout: number; // Max time to wait} const connectivityChecks: ConnectivityCheck[] = [ { name: "Database connectivity", required: true, timeout: 10000, check: async (config) => { try { const client = new PostgresClient(config.database); await client.connect(); await client.query("SELECT 1"); await client.end(); return { reachable: true }; } catch (error) { return { reachable: false, error: `Cannot connect to database at ${config.database.host}:${config.database.port}: ${error.message}`, suggestions: [ "Verify database server is running", "Check network connectivity and firewalls", "Verify credentials are correct", "Check if database name exists", ], }; } }, }, { name: "Redis connectivity", required: (config) => config.cache.enabled && config.cache.backend === "redis", timeout: 5000, check: async (config) => { if (!config.cache.enabled || config.cache.backend !== "redis") { return { reachable: true, skipped: true }; } try { const redis = new Redis(config.cache.redisHost, config.cache.redisPort); await redis.ping(); await redis.quit(); return { reachable: true }; } catch (error) { return { reachable: false, error: `Cannot connect to Redis at ${config.cache.redisHost}:${config.cache.redisPort}`, }; } }, }, { name: "External API availability", required: false, // Warning only timeout: 5000, check: async (config) => { try { const response = await fetch(`${config.payments.apiUrl}/health`, { method: "GET", signal: AbortSignal.timeout(5000), }); if (!response.ok) { return { reachable: false, error: `Payment API returned status ${response.status}`, }; } return { reachable: true }; } catch (error) { return { reachable: false, error: `Cannot reach payment API: ${error.message}`, }; } }, },]; async function validateConnectivity(config: AppConfig): Promise<ConnectivityValidationResult> { const results: ConnectivityCheckResult[] = []; const errors: string[] = []; const warnings: string[] = []; // Run checks in parallel for speed const checkPromises = connectivityChecks.map(async (check) => { console.log(` Checking ${check.name}...`); try { const result = await Promise.race([ check.check(config), new Promise<ConnectivityResult>((_, reject) => setTimeout(() => reject(new Error("Timeout")), check.timeout) ), ]); if (!result.reachable && !result.skipped) { const message = `[${check.name}] ${result.error}`; if (check.required) { errors.push(message); } else { warnings.push(message); } } return { check: check.name, result }; } catch (error) { const message = `[${check.name}] Check timed out after ${check.timeout}ms`; if (check.required) { errors.push(message); } else { warnings.push(message); } return { check: check.name, result: { reachable: false, error: "Timeout" } }; } }); await Promise.all(checkPromises); return { valid: errors.length === 0, errors, warnings };}Connectivity checks add to startup time. Run them in parallel with timeouts to avoid slow startup when services are temporarily unavailable. In some environments (like during container orchestration), external services might not be ready immediately—consider retry logic for critical dependencies.
Bringing all validation layers together, here's a complete startup validation sequence that ensures applications never start with invalid configuration.
Production-ready validation sequence:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
async function validateAndLoadConfiguration(): Promise<AppConfig> { console.log("=== Configuration Validation ==="); const startTime = Date.now(); try { // Step 1: Load raw configuration from all sources console.log("Loading configuration..."); const rawConfig = await loadRawConfiguration(); // Step 2: Schema validation console.log("Validating schema..."); const schemaResult = AppConfigSchema.safeParse(rawConfig); if (!schemaResult.success) { const errors = formatZodErrors(schemaResult.error); throw new ConfigurationError("Schema validation failed", errors); } const config = schemaResult.data; // Step 3: Semantic validation console.log("Validating semantic rules..."); const semanticResult = validateSemantic(config); if (!semanticResult.valid) { throw new ConfigurationError("Semantic validation failed", semanticResult.errors); } for (const warning of semanticResult.warnings) { console.warn(` ⚠ ${warning}`); } // Step 4: Dependency validation console.log("Validating dependencies..."); const dependencyResult = validateDependencies(config); if (!dependencyResult.valid) { throw new ConfigurationError("Dependency validation failed", dependencyResult.errors); } // Step 5: Connectivity validation (can be async) console.log("Validating connectivity..."); const connectivityResult = await validateConnectivity(config); if (!connectivityResult.valid) { throw new ConfigurationError("Connectivity validation failed", connectivityResult.errors); } for (const warning of connectivityResult.warnings) { console.warn(` ⚠ ${warning}`); } // All validations passed const duration = Date.now() - startTime; console.log(`✅ Configuration validated successfully in ${duration}ms`); // Log configuration summary (without secrets) logConfigSummary(config); // Return frozen configuration return Object.freeze(config); } catch (error) { if (error instanceof ConfigurationError) { console.error("\n❌ CONFIGURATION ERROR - Application startup blocked"); console.error("Errors:"); for (const err of error.errors) { console.error(` ✗ ${err}`); } console.error("\nApplication cannot start with invalid configuration."); process.exit(1); } throw error; }} class ConfigurationError extends Error { constructor(message: string, public errors: string[]) { super(message); this.name = "ConfigurationError"; }} function logConfigSummary(config: AppConfig): void { console.log("\nConfiguration Summary:"); console.log(` Environment: ${config.environment}`); console.log(` Database: ${config.database.host}:${config.database.port}/${config.database.database}`); console.log(` Server: ${config.server.http.host}:${config.server.http.port}`); console.log(` TLS: ${config.server.tls?.enabled ? "enabled" : "disabled"}`); console.log(` Cache: ${config.cache.enabled ? config.cache.backend : "disabled"}`); console.log(` Log Level: ${config.logging.level}`); // Never log secrets} // Application entry pointasync function main(): Promise<void> { const config = await validateAndLoadConfiguration(); // Configuration is guaranteed valid at this point const app = createApplication(config); await app.start();}How you present validation errors determines whether operators can quickly fix issues or spend hours debugging. Invest in clear, actionable error messages.
Effective error presentation principles:
database.pool.maxConnections is much more helpful than just maxConnections.12345678910111213141516171819202122232425262728293031
❌ CONFIGURATION ERROR - Application startup blocked Errors found: 4 ✗ [Schema] database.port: Expected number, received string Value: "five-four-three-two" Source: Environment variable APP_DATABASE_PORT Fix: Set APP_DATABASE_PORT to a valid port number (1-65535) ✗ [Schema] server.http.port: Required field missing Source: Not found in config file or environment Fix: Set APP_SERVER_HTTP_PORT or add server.http.port to config.yaml ✗ [Semantic] Cache TTL shorter than refresh interval ttlSeconds: 60, refreshIntervalSeconds: 120 Fix: Increase ttlSeconds to be greater than refreshIntervalSeconds ✗ [Connectivity] Cannot connect to database at db.staging.internal:5432 Error: Connection refused Suggestions: - Verify database server is running - Check network connectivity and firewalls - Verify VPN connection if required Warnings: 1 ⚠ [Semantic] Database pool size (5) may be too small for production load Consider increasing database.pool.maxConnections Application cannot start with invalid configuration.See documentation: https://docs.example.com/configConfiguration validation is your first line of defense against deployment failures. By validating comprehensively at startup, you shift error discovery from mysterious runtime failures to clear startup messages—when fixing is cheap and safe.
Module Complete:
You've now completed the Configuration Management module. You understand:
These skills enable you to build systems that are configurable where needed while remaining simple, debuggable, and reliable. Configuration done well is invisible—operators can deploy with confidence, and developers can reason about behavior deterministically.
Congratulations! You've mastered Configuration Management. You can now design configuration systems that are robust, type-safe, environment-aware, and thoroughly validated. Your applications will fail fast with clear messages when misconfigured, and run reliably when configured correctly.