Day 62 — net/http Server Configuration
Learning Objectives
- Configure all four
http.Servertimeouts — each prevents a different attack - Use
ConnStatecallback to track connection lifecycle - Understand why default
http.Serveris insecure for production
Key Concepts
http.Server timeout fields and what they prevent:
ReadHeaderTimeout: 5s — Slowloris: attacker sends headers 1 byte/sec
ReadTimeout: 30s — Slow body: attacker sends body 1 byte/sec
WriteTimeout: 30s — Slow read: attacker reads response 1 byte/sec
IdleTimeout: 120s — Connection leeching: attacker holds keep-alive open
MaxHeaderBytes: 64KB — Header bomb: attacker sends huge headers
Without these:
Slowloris + 10,000 connections = server out of file descriptors
Default http.Server has NO timeouts — use ONLY with a reverse proxy in front
Exercise: Hardened HTTP Server
package main
import (
"fmt"
"net/http"
"sync/atomic"
"time"
)
type ConnStats struct {
New atomic.Int64
Active atomic.Int64
Idle atomic.Int64
Closed atomic.Int64
}
func (cs *ConnStats) Handler(conn net.Conn, state http.ConnState) {
switch state {
case http.StateNew:
cs.New.Add(1)
cs.Active.Add(1)
case http.StateActive:
cs.Idle.Add(-1)
case http.StateIdle:
cs.Active.Add(-1)
cs.Idle.Add(1)
case http.StateClosed, http.StateHijacked:
cs.Active.Add(-1)
cs.Idle.Add(-1)
cs.Closed.Add(1)
}
}
func main() {
stats := &ConnStats{}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "OK\n")
})
mux.HandleFunc("/conn-stats", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "new=%d active=%d idle=%d closed=%d\n",
stats.New.Load(), stats.Active.Load(), stats.Idle.Load(), stats.Closed.Load())
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
// SECURITY: all timeouts must be set in production
ReadHeaderTimeout: 5 * time.Second, // Slowloris defense
ReadTimeout: 30 * time.Second, // slow body defense
WriteTimeout: 30 * time.Second, // slow client defense
IdleTimeout: 120 * time.Second, // keep-alive limit
MaxHeaderBytes: 64 * 1024, // 64KB header limit
// Connection lifecycle monitoring
ConnState: stats.Handler,
}
fmt.Println("Hardened HTTP server on :8080")
fmt.Println("Test Slowloris: pip install slowloris && slowloris -p 8080 localhost")
fmt.Println(" With ReadHeaderTimeout=5s: server stays alive")
fmt.Println(" Without timeout: server dies in ~30s")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
fmt.Println("ERROR:", err)
}
}
Real-World Context
The default http.ListenAndServe(":8080", handler) creates a server with NO timeouts. Behind a properly configured nginx or ALB, this is fine. Directly on the internet: Slowloris will take it down in minutes. PCI DSS and SOC 2 scans specifically check for missing timeout configurations.