Skip to content

Production Checklist

Walking through these items before launch effectively eliminates common security-configuration gaps. This checklist is grouped by category, with each item annotated with its default value, recommended production value, and a verification method. Consider automating the high-risk items in CI via the script at the end.

TLS / Encryption

Check itemDefaultRecommended production valueVerification method
InsecureSkipVerifyfalsefalseCode search; see command at the end
MinTLSVersionTLS 1.2TLS 1.2+ (1.3 for high security)grep MinTLSVersion
MaxTLSVersionTLS 1.3TLS 1.3grep MaxTLSVersion
Not using TestingConfig()YesCode search; see command at the end
Certificate pinning (high-security scenarios)Not enabledRecommendedgrep CertificatePinner

WARNING

InsecureSkipVerify = true invalidates all TLS security measures. HTTPC prints a warning to stderr in non-test environments — confirm no such warning appears in the logs before launch.

SSRF Protection

Check itemDefaultRecommended production valueVerification method
AllowPrivateIPsfalsefalse (when handling untrusted URLs)Code search; see command at the end
SSRFExemptCIDRsnilList only necessary subnets, as narrowly as possibleAudit whether subnets can be narrowed
Using SecureConfig() for user URLsYesCode review
RedirectWhitelistnilConfigure when handling user URLsCode review
go
cfg := httpc.DefaultConfig()
cfg.Security.AllowPrivateIPs = false
// Exempt only subnets you actually need, as narrowly as possible
cfg.Security.SSRFExemptCIDRs = []string{"10.50.0.0/16"}
cfg.Security.RedirectWhitelist = []string{"api.example.com"}

Timeout Configuration

Timeouts are the first line of defense against Slowloris, resource exhaustion, and cascading failures.

Check itemDefaultRecommended production valueVerification method
Timeouts.Request180sSet per business need (e.g. 30s)Confirm non-zero
Timeouts.Dial10s5-10sgrep Timeouts.Dial
Timeouts.TLSHandshake10s5-10sgrep Timeouts.TLSHandshake
Timeouts.ResponseHeader0As needed (see below)Understand its scope
Timeouts.IdleConn90s60-120s

WARNING

Timeouts.ResponseHeader is a transport-level hard cap that applies to all requests on the client and cannot be overridden per request with WithTimeout. Setting a positive value overrides a longer WithTimeout. Only set a positive value when you need transport-level defense against Slowloris; for long-response scenarios like AI APIs, set it to 0 and rely on the Request timeout.

Size Limits

Check itemDefaultRecommended production valueVerification method
MaxResponseBodySize10MBPer business need (e.g. 5MB)Confirm non-zero
MaxDecompressedBodySize100MBPer business need (e.g. 50MB)Confirm non-zero
MaxRequestBodySize0 (unlimited)Explicitly set an upload capgrep MaxRequestBodySize
MaxResponseHeaderBytes0 (Go default 10MB)Tighten to 1MB for high securitygrep MaxResponseHeaderBytes
go
cfg := httpc.DefaultConfig()
cfg.Security.MaxResponseBodySize = 5 * 1024 * 1024     // 5MB response
cfg.Security.MaxDecompressedBodySize = 50 * 1024 * 1024 // 50MB decompressed
cfg.Security.MaxRequestBodySize = 2 * 1024 * 1024       // 2MB upload (default 0 = unlimited!)
cfg.Connection.MaxResponseHeaderBytes = 1 * 1024 * 1024  // 1MB response header

DANGER

MaxRequestBodySize defaults to 0 (unlimited) and has no automatic fallback. If you proxy forwarded requests or handle user uploads without setting it, an attacker can send oversized requests to drain bandwidth and memory. Always set it explicitly.

Retry Strategy

Check itemDefaultRecommended production valueVerification method
MaxRetries3No more than 5Code review
Retrying non-idempotent requestsBe cautious with POST/PUT/PATCHCode review for idempotency
EnableJittertruetrue (prevents thundering herd)grep EnableJitter
MaxRetryDelay30s30s

WARNING

Retrying non-idempotent requests (POST creating a resource, PUT partial update) may cause duplicate creation or duplicate charges. If the operation is not idempotent, set WithMaxRetries(0) for such requests, or implement an idempotency key on the server side.

Check itemDefaultRecommended production valueVerification method
CookieSecuritynil (no validation)StrictCookieSecurityConfig()grep CookieSecurity
WithSecureCookie orderingAfter all WithCookie callsCode review
go
cfg := httpc.DefaultConfig()
cfg.Security.CookieSecurity = httpc.StrictCookieSecurityConfig()
// Requires Secure + HttpOnly + SameSite=Strict

File Download Security

Check itemDefaultRecommended production valueVerification method
Untrusted download pathUse only trusted paths; never concatenate user inputCode review
Checksum verificationNot setSet SHA-256 for critical filesgrep Checksum
Overwrite / ResumeDownloadfalseAs neededCode review

HTTPC's Download already includes five layers of path defense (UNC blocking, control-character filtering, system-path protection, path-traversal detection, symlink defense), but you should still avoid using user input directly as FilePath.

Resource Management

Check itemDefaultRecommended production valueVerification method
Explicit client.Close()defer client.Close()Code review
Closing the default clientCloseDefaultClient() on long-running service exitCode review
WithContext for cancellationYesCode review
go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/cybergodev/httpc"
)

func main() {
	cfg := httpc.DefaultConfig()
	client, err := httpc.New(cfg)
	if err != nil {
		log.Fatal(err)
	}
	// Ensure the connection pool is released
	defer func() {
		if cerr := client.Close(); cerr != nil {
			log.Printf("failed to close client: %v", cerr)
		}
	}()

	// Use context to control per-request timeout and cancellation
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	result, err := client.Get("https://api.example.com", httpc.WithContext(ctx))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("status code: %d\n", result.StatusCode())
}

TIP

When using package-level functions (httpc.Get, etc.), the default client does not close connections automatically on program exit. Long-running services should call httpc.CloseDefaultClient() during graceful shutdown to release the connection pool. For production services, prefer creating an explicit client via httpc.New(cfg) to control configuration and lifecycle.

Monitoring and Auditing

Audit Middleware (High-Security Scenarios)

AuditMiddleware generates structured audit events suited to compliance-heavy scenarios. The URL in the event is sanitized (credentials removed), and sensitive request headers are masked by default.

go
auditCfg := httpc.DefaultAuditConfig()
auditCfg.OnAudit = func(event httpc.AuditEvent) {
    // event.SourceIP / event.UserID are injected from context
    data, _ := json.Marshal(event)
    log.Println(string(data))
}
auditCfg.Format = "json"
auditCfg.IncludeHeaders = true
auditCfg.MaskHeaders = []string{"Authorization", "Cookie", "Set-Cookie", "X-API-Key"}
auditMiddleware := httpc.AuditMiddleware(auditCfg)

SourceIP and UserID are injected via the context keys httpc.SourceIPKey and httpc.UserIDKey, making it easy to correlate requests with callers. AuditEvent includes timestamp, method, URL, status code, duration, retry count, error, redirect chain, and request/response headers.

Logging and Metrics Middleware

Check itemRecommended production valueVerification method
RecoveryMiddleware()Enable (prevents panic crashes)grep RecoveryMiddleware
LoggingMiddleware()Enable (request logs)grep LoggingMiddleware
MetricsMiddleware()Enable (collect metrics)grep MetricsMiddleware
RequestIDMiddleware()Enable (request tracing)grep RequestIDMiddleware

Certificate Pinning

For high-security scenarios (finance, healthcare), enabling certificate pinning is recommended to defend against MITM attacks after a CA compromise:

go
pinner, err := httpc.NewSPKIHashPinner(
    "YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2fuihg=", // current key
    "C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=", // backup (rotation)
)
if err != nil {
    log.Fatal(err)
}
cfg := httpc.DefaultConfig()
cfg.Security.CertificatePinner = pinner

For pinning configuration and maintenance details, see TLS and Certificate Pinning.

Code Examples

Production-Grade Client Creation

go
package main

import (
	"log"
	"time"

	"github.com/cybergodev/httpc"
)

func main() {
	cfg := httpc.DefaultConfig()

	// Timeouts
	cfg.Timeouts.Request = 30 * time.Second
	cfg.Timeouts.Dial = 10 * time.Second
	cfg.Timeouts.TLSHandshake = 10 * time.Second
	cfg.Timeouts.ResponseHeader = 0 // Rely on Request timeout; no transport-level hard cap
	cfg.Timeouts.IdleConn = 90 * time.Second

	// Connection pool
	cfg.Connection.MaxIdleConns = 50
	cfg.Connection.MaxConnsPerHost = 10

	// Security
	cfg.Security.AllowPrivateIPs = false
	cfg.Security.MaxResponseBodySize = 5 * 1024 * 1024      // 5MB
	cfg.Security.MaxDecompressedBodySize = 50 * 1024 * 1024 // 50MB
	cfg.Security.MaxRequestBodySize = 2 * 1024 * 1024       // 2MB upload

	// Retries
	cfg.Retry.MaxRetries = 3
	cfg.Retry.Delay = 1 * time.Second
	cfg.Retry.EnableJitter = true

	// Request defaults
	cfg.Defaults.UserAgent = "my-service/1.0"
	cfg.Defaults.FollowRedirects = true
	cfg.Defaults.MaxRedirects = 5

	// Middleware
	cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{
		httpc.RecoveryMiddleware(),
		httpc.LoggingMiddleware(&httpc.LoggingConfig{LogFunc: log.Printf}),
		httpc.RequestIDMiddleware(httpc.DefaultRequestIDConfig()),
	}

	client, err := httpc.New(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()
	log.Println("production client ready")
}

Secure Client (Handling User URLs)

go
func createSecureClient() (httpc.Client, error) {
	cfg := httpc.SecureConfig()
	cfg.Security.CookieSecurity = httpc.StrictCookieSecurityConfig()
	cfg.Security.RedirectWhitelist = []string{"api.example.com"}
	// SecureConfig already sets FollowRedirects = false, AllowPrivateIPs = false, 5MB response cap
	return httpc.New(cfg)
}

Check Commands

Run the following commands in CI or before commit to scan for high-risk configurations:

bash
# Check for misuse of TestingConfig (excluding test files)
grep -r "TestingConfig" --include="*.go" | grep -v "_test.go"

# Check InsecureSkipVerify = true
grep -rn "InsecureSkipVerify.*true\|InsecureSkipVerify:\s*true" --include="*.go" | grep -v "_test.go"

# Check AllowPrivateIPs = true (dangerous in production)
grep -rn "AllowPrivateIPs.*true\|AllowPrivateIPs:\s*true" --include="*.go" | grep -v "_test.go"

# Check whether MaxRequestBodySize is set (default 0 = unlimited)
grep -rn "MaxRequestBodySize" --include="*.go" | grep -v "_test.go"

TIP

Consider wrapping these commands as a CI step that fails the build when high-risk configurations (TestingConfig, InsecureSkipVerify: true, AllowPrivateIPs: true appearing in non-test code) are detected.

Next Steps