Software Securityhome
Fix a findingSearch
Phase 5 · Release

Secure defaults

Secure defaults means the configuration you ship is the safe one: debug output off, generic errors, no shared default passwords, security headers on every response, and risky features off until someone enables them. CISA calls this secure by default: resilient against prevalent exploitation techniques out of the box.

Many leaks come from a setting nobody changed, not from code. Express sends stack traces unless NODE_ENV=production, Django's startproject writes DEBUG = True, and Ignition before 2.5.2, Laravel's debug error page, let unauthenticated attackers run code on sites with debug mode on (CVE-2021-3129). Code review often misses environment files and proxy settings; a check of the deployed system sees them.

Who
Developers and platform engineers who own the deployment configuration, with AppSec setting the baseline
When
Before each release, against the production configuration and the real path through CDN, proxy and application
How often
Every deployment, automated; the baseline reviewed when a framework, proxy or feature changes
You end up with
A written baseline kept as configuration as code, and a release check that proves headers, error pages and exposed paths

Do this

  1. Write the production baseline down

    List each setting that affects security and its production value, per framework and server: Django DEBUG = False, NODE_ENV=production, ASPNETCORE_ENVIRONMENT=Production, Spring Boot Actuator exposing only health, nginx autoindex off. Keep it in the repository as configuration as code, so every change is reviewed. NIST SSDF PW.9 asks for this: a secure baseline, implemented as the default and documented for administrators.

  2. Turn off debug output and verbose errors

    Set each framework's production switch explicitly, even where the default is safe, and add a final error handler that logs the detail with a reference ID and returns only that ID. Check the error pages of the proxy and CDN too. ASVS 5.0 requirement V13.4.2 asks that debug modes be disabled in production, and Django's settings reference says never to deploy with DEBUG on.

  3. Ship only what the service needs

    Deploy a clean build directory, never a working tree: no .git, .env, backups or public source maps. Delete sample apps and default accounts, keep monitoring and API documentation endpoints such as Actuator, pprof and Swagger UI internal, and turn off directory listings and HTTP TRACE. Enforce the same rules at the web server, so a mistake in the build is caught at the edge.

  4. Send security headers on every response

    Send Strict-Transport-Security, a nonce- or hash-based Content-Security-Policy with frame-ancestors, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin and a Permissions-Policy that disables unused features; remove X-Powered-By and version numbers in Server. Roll CSP out in report-only mode first, and set the headers at a layer that also covers 404s, redirects and static files.

  5. Make risky options opt-in

    For software that customers install or configure, ship no shared default password: have the administrator set one during setup, or issue a one-time setup token. Keep dangerous features off, with a warning that names the risk when someone turns one on. CISA recommends folding hardening-guide steps into the default configuration, and publishing loosening guides that explain the risk of each change instead.

  6. Check every deployment from outside

    After each deployment, send requests through the real CDN and proxy: a URL that throws, /.git/HEAD, /.env, the .map file of your main bundle and known admin paths. Inspect the response headers with curl -sI or Security headers analyzer. Run framework checks such as Django's manage.py check --deploy against the production settings in CI, and fail the release on any finding.

Done when

A pre-release check of the deployed system confirms the headers, error pages and exposed paths, and a failure blocks the release.

Mark it in the gap check

Common mistakes

  • Trusting a framework default instead of setting the value. A container image or .env file that sets a development environment, APP_DEBUG=true or no NODE_ENV turns detailed errors back on without any code change.
  • Checking the application but not the edge. Proxies, CDNs and default error handlers can drop security headers on 404s and redirects, or serve files from the build directory that the application never would.
  • Opening Spring Boot Actuator with include=* so a health check works. It also exposes env, configprops and threaddump; keep health public and put the rest on an internal port behind authentication.
  • Publishing a hardening guide instead of changing the default. Customers who never read it run the unsafe configuration, and CISA warns that hardening guides can also show attackers which insecure features to look for.
  • Removing version banners and calling the work done. Banners only slow reconnaissance; turning off debug output and patching are the controls.

The production switch in common frameworks

Set each one explicitly in the production configuration, even where the default is safe, and make the deployment fail when it is wrong. Checked against each framework's documentation in October 2026.

The production switch in common frameworks: table
FrameworkProduction settingSafe if unset?
DjangoDEBUG = False, with ALLOWED_HOSTS setYes, but startproject writes DEBUG = True
FlaskNo --debug or debug=True; serve with a production WSGI serverYes, but the Werkzeug debugger runs arbitrary code if it is ever on
ExpressNODE_ENV=production and your own final error handlerNo: otherwise the built-in handler sends the stack trace
ASP.NET CoreASPNETCORE_ENVIRONMENT=ProductionYes: with no environment set, Production is the default
Spring BootActuator exposure health only; show-values left at neverYes: only health is exposed over HTTP
LaravelAPP_DEBUG=falseYes in config/app.php, but .env files often set it to true
Railsconfig.consider_all_requests_local = falseYes: false by default in production
PHPdisplay_errors = Off, log_errors = On, expose_php = OffNo: display_errors is On without a php.ini, and php.ini-production leaves expose_php = On

Tools and guides

Standards

NIST SSDF 1.1
PW.9 Configure Software to Have Secure Settings by Default

Sources

  1. CISA and partners: Shifting the Balance of Cybersecurity Risk (Secure by Design and Secure by Default). Revised 25 October 2023.
  2. OWASP Top 10:2025 A02 Security Misconfiguration. OWASP Top 10:2025.
  3. OWASP ASVS 5.0.0: V13 Configuration (V13.4 Unintended Information Leakage). Version 5.0.0.
  4. OWASP HTTP Security Response Headers Cheat Sheet
  5. OWASP Secure Headers Project
  6. NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1, practice PW.9. February 2022.
  7. OWASP SAMM: Environment Management
  8. CVE-2021-3129: Ignition remote code execution in Laravel debug mode (CVE record)
  9. Django settings reference: DEBUG. Django 6.1.
  10. Django deployment checklist (check --deploy). Django 6.1.
  11. Werkzeug documentation: Debugging applications
  12. Express guide: Error handling
  13. Microsoft Learn: ASP.NET Core runtime environments. ASP.NET Core 10.0.
  14. Spring Boot reference: Actuator endpoints
  15. Laravel 13 documentation: Configuration, debug mode. Laravel 13.x.
  16. Rails guides: Configuring Rails applications (consider_all_requests_local)
  17. php-src: php.ini-production

Find your next answer

Type to search. Search runs in your browser.

Searches run in your browser, but the host can infer them from the index files they load. The address shows your search so you can bookmark or share it.