Why this matters
Secure defaults and hardening reduce risk without relying on every engineer to remember every setting. In Backend & Platform work, you will:
- Stand up new services that must be safe on day one (TLS, headers, least-privileged roles).
- Ship infrastructure templates used by many teams (Dockerfiles, Kubernetes manifests, DB policies).
- Pass compliance audits by proving safe-by-default configurations and automated checks.
- Contain incidents: hardened systems limit blast radius and speed recovery.
Concept explained simply
Secure-by-default means the safest behavior happens automatically, without extra steps. Hardening is removing everything unnecessary and tightening what remains.
Mental model
- Default stance: "locked". You only open what is explicitly required.
- Layers: code -> service -> runtime/container -> network -> data. Each layer blocks and logs.
- Paved road: templates and checks that make the secure path the easy path.
Core principles (with quick checks)
Least privilege
Give each process, role, and service only the access it needs, nothing more.
- IAM roles scoped to a single service/task.
- Database roles limited to needed tables and actions.
- Containers run as non-root with minimal capabilities.
Fail closed
On errors or timeouts, deny access by default.
- Auth middleware denies if token validation errors out.
- Network policies default deny, then add allow rules.
Defense in depth
Multiple independent controls catch mistakes.
- AuthZ in API + DB row-level policies.
- WAF/rate limits + input validation + prepared statements.
Immutability and patching
Ship immutable images, patch often, and rebuild images instead of changing live nodes.
Secure transport and storage
TLS by default in transit; encrypt sensitive data at rest with managed keys and rotation.
Secrets management
Use a vault/KMS, short-lived credentials, and prevent secrets in logs or images.
Safe-by-default logging
Never log secrets or PII by default. Redact and sample.
Measurable baselines
Codify baselines as templates and policies. Continuously check with CI and scanners.
Worked examples
Example 1: Hardening a public HTTP API
Goal: enforce TLS, add safe headers, secure cookies, and rate limits.
Before
# Nginx (simplified)
server {
listen 80;
server_name api.example;
location / {
proxy_pass http://app:3000;
}
}
// Express app
app.use((req,res,next)=>{
res.cookie('sid', token); // no flags
next();
});
After
# Nginx
server {
listen 80;
return 301 https://$host$request_uri; # force HTTPS
}
server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer;
add_header Content-Security-Policy "default-src 'none'" always;
# Basic rate limit
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://app:3000;
}
}
// Express
app.use((req,res,next)=>{
res.cookie('sid', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
next();
});
- Default is HTTPS only.
- Headers reduce XSS/Clickjacking risk.
- Cookies are protected by default.
- Rate limiting reduces brute force and abuse.
Example 2: Locking down PostgreSQL
Goal: TLS, least privilege, and safe authentication.
# pg_hba.conf (snippet)
# Reject by default; allow only TLS from app subnet
hostssl all app_user 10.10.0.0/24 scram-sha-256
# No 'trust' entries; no wide 0.0.0.0/0
-- SQL
CREATE ROLE app_user LOGIN PASSWORD 'use-managed-secret';
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA app_schema TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app_schema TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA app_schema
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
-- Optional row-level security
ALTER TABLE app_schema.orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON app_schema.orders
USING (tenant_id = current_setting('app.tenant_id')::uuid);
- No plaintext auth; use SCRAM over TLS.
- Public access revoked; schema-scoped grants.
- RLS for multi-tenant isolation.
Example 3: Container and Kubernetes baseline
Goal: run as non-root, restrict filesystem, and block network by default.
# Dockerfile
FROM gcr.io/distroless/nodejs20
USER 10001:10001
ENV NODE_ENV=production
# Copy only needed files
COPY --chown=10001:10001 build/ /app/
WORKDIR /app
CMD ["server.js"]
# Deployment (snippet)
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: registry/api:1.2.3
ports:
- containerPort: 3000
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
livenessProbe:
httpGet: { path: /healthz, port: 3000 }
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 3000 }
initialDelaySeconds: 5
periodSeconds: 5
# NetworkPolicy (default deny + allow only needed egress)
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata: { name: api-deny-by-default }
spec:
podSelector: { matchLabels: { app: api } }
policyTypes: [Ingress, Egress]
ingress: []
egress:
- to:
- namespaceSelector: { matchLabels: { name: "db" } }
podSelector: { matchLabels: { app: postgres } }
ports: [{ protocol: TCP, port: 5432 }]
- Non-root + read-only reduces impact of RCE.
- NetworkPolicy blocks surprise egress.
- Resource limits prevent noisy-neighbor attacks.
Hands-on exercises
Everyone can do these exercises. If you are logged in, your progress will be saved.
- Exercise ex1: Harden an HTTP service config (see details and submit your improved version).
- Exercise ex2: Create least-privilege PostgreSQL roles and auth settings.
Checklist to track your work
Common mistakes and self-check
- Allow-all egress from services
Self-check: Is there a network policy/firewall rule that blocks unknown destinations by default? - Running containers as root
Self-check: Does your pod/deployment set runAsNonRoot and drop capabilities? - Using default DB roles or wide grants
Self-check: Can the app user drop tables it should not own? - Logging secrets/PII
Self-check: Search logs for tokens, passwords, or IDs. Are redaction filters enabled by default? - Unpinned dependencies and missing patches
Self-check: Are base images and libs pinned and scanned during CI? - Fail-open auth
Self-check: If the JWKS fetch fails, does the request get denied?
Practical projects
- Project A: Build a secure service template (Dockerfile, K8s manifests, gateway config) that enforces TLS, headers, non-root, read-only FS, and default-deny network. Acceptance: one command to scaffold, all checks green in CI.
- Project B: Database baseline. Provision a dev Postgres with TLS, SCRAM, least-privilege user, and an automated script that verifies grants. Acceptance: running the verifier shows no high-risk findings.
- Project C: CI policy checks. Add linters/scanners to reject images with root user or missing securityContext. Acceptance: a failing example PR is blocked automatically.
Mini challenge
You are launching a new microservice that calls one external API and a Postgres database. In 15 minutes, write down the five defaults you will enforce before the first deploy and why. Bonus: draft a one-page baseline policy other teams can reuse.
Mini challenge hints
- HTTPS-only with HSTS.
- NetworkPolicy: allow only Postgres and the external API CIDR.
- DB role limited to CRUD on one schema.
- Non-root + read-only FS + runtime/default seccomp.
- Rate limiting and safe headers.
Who this is for
- Backend engineers shipping services to production.
- Platform/SRE engineers creating templates and paved roads.
- Engineers preparing for compliance or internal security reviews.
Prerequisites
- Basic HTTP/HTTPS and TLS understanding.
- Comfort with Linux, containers, and YAML.
- Familiarity with a relational database (e.g., PostgreSQL).
- Basic authN/authZ concepts and environment configuration.
Learning path
- Read this lesson and study the worked examples.
- Complete the exercises and turn them into reusable templates.
- Add automated checks in CI to enforce the baseline.
- Take the Quick Test to confirm understanding.
- Apply the baseline to two existing services and measure improvements.
Next steps
- Automate: convert your checklists into CI policies and admission controls.
- Expand coverage: include secrets rotation, SSA/SSRF protections, and input validation libraries by default.
- Review regularly: schedule quarterly baseline reviews and patch cycles.
Quick Test
The Quick Test below is available to everyone. Log in to save your progress and resume later.