Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
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.
ASP.NET Core Behind a Reverse Proxy: Forwarded Headers, KnownIPNetworks and the Real Client IP
An app behind a proxy sees the client's IP and HTTPS only if UseForwardedHeaders is set up correctly: you must say which headers to process and add the proxy's address to KnownProxies or, on .NET 10, KnownIPNetworks. With real runs we look at the HTTPS redirection loop, the ASPDEPR005 warning, fake X-Forwarded-For values and ForwardLimit, PathBase via X-Forwarded-Prefix and Data Protection keys across multiple instances.
Getting to Know Kestrel: Endpoints, ASPNETCORE_HTTP_PORTS, Limits and HTTP/2–HTTP/3
Kestrel's listening address is decided by Listen in code, Kestrel:Endpoints, --urls/ASPNETCORE_URLS and ASPNETCORE_HTTP_PORTS, in that order, and every overridden setting shows up as a warning in the log. With nine runs we measure the precedence and look at PEM certificates and SNI, certificate renewal without a restart, default limits, HTTP/3 with libmsquic, connecting to Nginx over a Unix socket and the localhost trap in containers.
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.
End-to-End Zero Downtime: Deploying Bookstore with Blue-Green and Canary via GitHub Actions
All pieces of the series in a single GitHub Actions workflow: build the image once, a migration gate and an expand migration bundle, deploy to the idle color, a smoke test through the preview, 10% and 50% canary gates, a full switch and a manual rollback job. Run locally with act, the pipeline failed four times before succeeding: a gate scanning Down(), act's artifact v7 incompatibility, a preview pointing at the old color, and too little canary traffic.
Rollback or Roll Forward? Rollback Plans, Kill Switches and Automated Rollback Criteria
There are four ways back: kill switch, artifact rollback, roll forward and limiting the impact; the choice depends on flags, the readiness of the previous artifact and schema compatibility. An atomic symlink switch, a Down migration that restores the column but not its 300,004 names, git revert of a merge commit, an automated trigger rolling back a 20%-failing version in 10 s (18 of 85 requests failed), a runbook template and measuring with DORA.
Feature Flags: Decoupling Deploy from Release with Microsoft.FeatureManagement and OpenFeature
A feature flag lets you deploy code and release behavior with a config change. Targeting with Microsoft.FeatureManagement 4.8 (10.1% of 1000 users, sticky per user), variants, WithFeatureGate on minimal APIs and a kill switch that takes effect in 0.4 s; a signal-based flag service and a tested structural directive in Angular; the targeting boolean flag returning false through OpenFeature's preview provider, and flag debt.
Where Migrations Fit in a Deploy: Expand-Contract, EF Core Migration Bundles and Backward-Compatible Schemas
Safely splitting a column while two versions run side by side takes three deploys: add and dual-write, switch to the new columns, remove the old one. Real runs on PostgreSQL with 300,000 rows: v1 and v2 working together on one schema, the transaction error of a CONCURRENTLY index, deadlocks in parallel bundles because Npgsql's migration lock is tied to the transaction, 135 ms batches instead of an UPDATE locking for 8 s, and a review gate that stops DROP COLUMN.
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.