Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Choosing Hosting and a Production Checklist: Nginx, IIS, Caddy, Containers and PaaS
The hosting choice is decided first by what the team can operate, then by the number of services, deploy frequency and cost; a Linux VM with Nginx, IIS, Caddy, Traefik, YARP, PaaS and containers are different answers to the same questions. We try Caddy with seven lines of config and an active health check, and a ten-item pre-go-live check script catches a health endpoint invisible from outside and a missing default server on 443 in the seventh post's config.
HTTPS, Security Headers, Rate Limiting and Load Balancing in Nginx
With real runs: TLS termination in Nginx, HTTP/3 with http2 on and listen quic, security headers that don't disappear thanks to add_header_inherit merge, HSTS from a single place, 429s with limit_req, and round robin spreading unevenly without an upstream zone. ip_hash, the sticky cookie that came with 1.29.6 and passive health checks with max_fails: when an instance dies the client sees no error, but non-idempotent requests and unhealthy yet running processes need separate handling.
Serving Angular with Nginx: SPA Fallback, Cache Headers, Compression and an /api Proxy
Serving Angular with Nginx needs an SPA fallback with try_files, immutable caching for hashed bundles and no-cache for index.html; otherwise after a deploy a browser asking for an old chunk gets HTML with a 200. With real runs: gzip_static, a runtime config.json, a CORS-free setup with an /api proxy on the same origin, serving under a sub-path with base href, and the allowedHosts trap when putting Angular SSR on Node behind Nginx.
Nginx as a Reverse Proxy for ASP.NET Core: proxy_pass, Headers, WebSockets/SignalR and Timeouts
Since Nginx 1.29.7 upstream keepalive and HTTP/1.1 are the default, but only if you use an upstream block: proxy_pass to a direct address opens a new connection per request. We take Microsoft's reference config apart line by line and measure SignalR quietly falling back to long polling without the WebSocket upgrade map, buffering with SSE, aligning Nginx's and Kestrel's body and header limits, and telling 502 from 504 in the logs.
Nginx Fundamentals: Installation, nginx.conf Structure, server and location Blocks
Installing Nginx from the nginx.org repository gives you the current stable branch (1.30); Ubuntu 24.04's package stays on 1.24. With real runs we look at master and worker processes, nginx.conf contexts, rejecting unknown hosts with default_server, the location matching algorithm (=, longest prefix, ^~, regex), root versus alias, the trailing slash in proxy_pass, and how reload and restart behave differently with a broken config.
Web Servers, Application Servers and Reverse Proxies: How Does an HTTP Request Reach Your App?
Kestrel is both a web server and an application server; a reverse proxy is the layer that accepts the request, forwards it to the app behind it and takes on TLS termination, static files, compression, buffering and load balancing. We send the same request straight to Kestrel and then through Nginx, compare the IP, headers and Angular deep-link behaviour the app sees, and put five hosting topologies side by side.
Canary Releases: Shifting Traffic by Percentage, Deciding with Metrics and Automated Analysis
A canary runs the new version with a small share of traffic and decides by comparing it with a control running at the same time. Weights in Nginx (900/100, not sticky), a user-sticky canary with split_clients and the empty-key trap, sticky cookies in 1.30; 4.80% vs 0% over 4,000 requests but a meaningless result over 19, a gate returning WAIT/ABORT/PROMOTE, Gateway API weights, and Argo Rollouts aborting a faulty version automatically.
Blue-Green Deployment: Two Environments with Nginx, App Service Slots and Instant Switchback
Blue-green installs the new version on the idle environment, runs a smoke test and switches traffic with a single reload; going back is the same reload. A preview port in Nginx, a smoke test with a version check, an error-free probe through switch and switchback, a 4 s request finishing on blue during the reload; App Service slot swap warm-up and slot setting pitfalls, a Service selector switch in Kubernetes, and the limit a shared database imposes.
Rolling Deployments: Updating Servers One by One, maxSurge and maxUnavailable in Kubernetes
A rolling deployment updates the fleet piece by piece; done right there is no outage, but two versions live side by side for a while. Three instances behind Nginx: a naive script dropped 2 of 90 GETs, while a script with drain and readiness checks passed 237 GETs and 90 POSTs without errors. Choosing maxSurge/maxUnavailable and minReadySeconds in Kubernetes, why exceeding progressDeadlineSeconds doesn't roll back, and when rolling is the wrong choice.
Scaling Out and Running SignalR in Production: Sticky Sessions, Redis Backplane, Azure SignalR Service and Monitoring
SignalR on multiple servers needs two things: sticky sessions that keep negotiate and the connection on the same server, and a backplane that carries messages between servers. Real error messages with Nginx, the skipNegotiation exception, the Redis backplane and its channel prefix, messages lost during a Redis outage, Azure SignalR Service, and SignalR metrics and tracing.
Server-Sent Events in Depth: TypedResults.ServerSentEvents, Resuming with Last-Event-ID and Proxy Pitfalls
To make up for missed SSE events, give every event an EventId, resend everything after the Last-Event-ID header and send a heartbeat as soon as the client connects. SseItem<T>, retry, CancellationToken, the Nginx gzip and timeout traps, HTTP/1.1's 6-connection limit, wrapping EventSource in an Observable in Angular, and reading with fetch when you need headers.
Angular Build and Deploy: ng build, Environment Files, Budgets and SPA Routing
Production bundles with ng build in Angular 12: the dist output, environment files via fileReplacements, budgets, base href, the SPA routing 404 on Nginx/IIS, source maps and bundle analysis.