Back

C5: Secure By Default Configurations - Configuration Examples

Each pair below shows an insecure default configuration and the secure default that replaces it, in the same technology. The goal is a system that is safe out of the box, so a default deployment needs no extra hardening.

1. Web Server (nginx)

Insecure

server {
    listen 80;                          # plaintext, no redirect to HTTPS
    server_tokens on;                   # advertises nginx version
    autoindex on;                       # browsable directory listings

    location / {
        root /var/www/html;             # serves .git, .env, backups too
    }
    # all HTTP methods accepted; no security headers
}

Secure

server { listen 80; return 301 https://$host$request_uri; }   # force HTTPS

server {
    listen 443 ssl;
    server_tokens off;                  # no version banner
    ssl_protocols TLSv1.2 TLSv1.3;      # modern TLS only
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none'" always;

    location / {
        root /var/www/html;
        autoindex off;                  # no directory listing
        limit_except GET POST { deny all; }        # deny-by-default methods
    }
    location ~ /\.(git|env|htaccess) { deny all; return 404; }
    location ~* \.(sql|bak|old|zip)$ { deny all; return 404; }
}

2. Application Framework (Django)

Insecure

# settings.py
DEBUG = True                            # tracebacks + debug pages to clients
ALLOWED_HOSTS = ['*']                   # accepts any Host header
SECRET_KEY = 'insecure-default-key'     # shipped default secret

SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
SECURE_HSTS_SECONDS = 0                 # no HSTS

Secure

# settings.py
import os
DEBUG = False                           # off by default in production
ALLOWED_HOSTS = ['app.example.com']     # explicit host allow-list
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']   # injected, never a default

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'

3. Cloud / IaC (Terraform — AWS S3)

Insecure

resource "aws_s3_bucket" "data" {
  bucket = "app-data"
}
# No public-access block  -> bucket can be made public
# No encryption block     -> objects stored unencrypted
resource "aws_s3_bucket_acl" "data" {
  bucket = aws_s3_bucket.data.id
  acl    = "public-read"                # world-readable
}

Secure

resource "aws_s3_bucket" "data" {
  bucket = "app-data"
}

resource "aws_s3_bucket_public_access_block" "data" {
  bucket                  = aws_s3_bucket.data.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true        # private by default
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id
  rule {
    apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" }
  }
}

4. Container (Dockerfile)

Insecure

FROM ubuntu:latest                      # full OS, floating tag, runs as root
COPY . /app
COPY .env /app/.env                     # secret baked into an image layer
WORKDIR /app
RUN apt-get update && apt-get install -y python3 curl netcat build-essential
CMD ["python3", "app.py"]               # process runs as root (uid 0)

Secure

FROM python:3.12-slim@sha256:<digest>   # minimal, patched, pinned by digest
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt   # only what's needed

COPY . .
# no secrets copied in; injected at runtime via env/secret store

RUN useradd --uid 10001 --no-create-home appuser
USER 10001                              # non-root by default
CMD ["python3", "app.py"]

5. Security Headers (Express middleware)

Insecure

const express = require('express');
const app = express();

// x-powered-by banner left on; no security headers; verbose errors
app.get('/api/me', (req, res) => res.json(loadUser()));
app.listen(3000);

Secure

const express = require('express');
const helmet = require('helmet');
const app = express();

app.disable('x-powered-by');            // drop the banner
app.use(helmet());                      // HSTS, nosniff, frameguard, CSP baseline
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
app.use(helmet.contentSecurityPolicy({
  directives: { defaultSrc: ["'none'"], frameAncestors: ["'none'"] }
}));

app.get('/api/me', (req, res, next) => {
  try { res.json(loadUser()); } catch (e) { next(e); }
});
app.use((err, req, res, next) => {      // generic error, detail to logs
  console.error(err);
  res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000);

6. Kubernetes Workload (Pod securityContext)

Insecure

apiVersion: v1
kind: Pod
metadata: { name: app }
spec:
  containers:
    - name: app
      image: app:latest
      # runs as root, writable root FS, all capabilities, privilege escalation on

Secure

apiVersion: v1
kind: Pod
metadata: { name: app }
spec:
  containers:
    - name: app
      image: app@sha256:<digest>
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities: { drop: ["ALL"] }   # deny-by-default capabilities

What Changed, and Why

LayerInsecure defaultSecure default
Web serverBanner on, listing on, all methods, plaintextBanner off, listing off, scoped methods, HTTPS + headers
FrameworkDebug on, wildcard hosts, shipped secretDebug off, host allow-list, injected secret, secure cookies
Cloud / IaCPublic-capable, unencrypted bucketPublic access blocked, encryption on
ContainerRoot, full OS, secrets baked inNon-root, minimal image, runtime secrets
HeadersNone; banner exposedHSTS, nosniff, CSP preset; banner removed
KubernetesRoot, all capabilities, writable FSNon-root, capabilities dropped, read-only FS

Next Steps