<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged nginx</title>
<link>https://coderlegion.com/tag/nginx</link>
<description>Powered by Question2Answer</description>
<item>
<title>Securing an Angular Application: Part 3 — Implementing CSP with Nginx</title>
<link>https://coderlegion.com/31601/securing-an-angular-application-part-3-implementing-csp-with-nginx</link>
<description>&lt;p&gt;Continuing my series on &lt;strong&gt;hardening Angular applications&lt;/strong&gt;, Part 3 moves into the actual &lt;strong&gt;Nginx server configuration and Content Security Policy (CSP)&lt;/strong&gt; implementation.&lt;/p&gt;
&lt;p&gt;In this part, I walk through the &lt;code&gt;docker-nginx-server.conf&lt;/code&gt; configuration used to serve a real Angular application and explain how the different pieces work together.&lt;/p&gt;
&lt;p&gt;Some of the topics covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚙️ Angular server configuration with Nginx&lt;/li&gt;
&lt;li&gt;🔀 Angular SPA routing&lt;/li&gt;
&lt;li&gt;📦 Static asset handling and caching&lt;/li&gt;
&lt;li&gt;🚫 Blocking hidden and sensitive files&lt;/li&gt;
&lt;li&gt;🔐 Security headers&lt;/li&gt;
&lt;li&gt;🛡️ Content Security Policy directives&lt;/li&gt;
&lt;li&gt;🔑 Dynamic CSP nonce handling&lt;/li&gt;
&lt;li&gt;🧩 Injecting the nonce into Angular HTML&lt;/li&gt;
&lt;li&gt;⚠️ Error page handling&lt;/li&gt;
&lt;li&gt;🔒 Trusted Types and other CSP protections&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than using a generic CSP policy, the goal is to understand &lt;strong&gt;which resources the Angular application actually needs and how those requirements translate into individual CSP directives&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This part also shows how the CSP nonce is handled using Nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;set $csp_nonce $request_id;

sub_filter_once off;
sub_filter '__CSP_NONCE__' '$csp_nonce';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows the nonce used by the CSP policy to be injected into the Angular application's HTML for each request.&lt;br&gt;
With the Nginx configuration and CSP in place, the next part moves one layer deeper into the deployment process: Dockerizing the Angular application and packaging the Nginx configuration into a production-ready container.&lt;/p&gt;
&lt;p&gt;📖 Read Part 3:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@jangidamit800/angular-content-security-policy-csp-implementing-the-nginx-server-configuration-c70593b9136a?sharedUserId=jangidamit800&quot;&gt;https://medium.com/@jangidamit800/angular-content-security-policy-csp-implementing-the-nginx-server-configuration-c70593b9136a?sharedUserId=jangidamit800&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; #Angular #AngularSecurity #CSP #ContentSecurityPolicy #Nginx #WebSecurity #DevSecOps #Docker #FrontendSecurity&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31601/securing-an-angular-application-part-3-implementing-csp-with-nginx</guid>
<pubDate>Sun, 18 Oct 2026 10:55:08 +0000</pubDate>
</item>
<item>
<title>Angular Security Headers: A Practical Guide to Securing Your Angular Application</title>
<link>https://coderlegion.com/30585/angular-security-headers-a-practical-guide-to-securing-your-angular-application</link>
<description>&lt;p&gt;I've started a new series focused on hardening Angular applications using browser and HTTP security controls.&lt;/p&gt;
&lt;p&gt;In Part 1, I cover the fundamentals of security headers and how they add another layer of protection between an Angular application and the browser.&lt;/p&gt;
&lt;p&gt;Some of the topics covered:&lt;/p&gt;
&lt;p&gt;🔒 HSTS — enforcing HTTPS&lt;br&gt;
🛡️ X-Content-Type-Options&lt;br&gt;
🧭 Referrer-Policy&lt;br&gt;
🎯 Permissions-Policy&lt;br&gt;
🚫 X-Frame-Options&lt;br&gt;
🔄 Cross-Origin Opener Policy (COOP)&lt;br&gt;
🌐 Cross-Origin Resource Policy (CORP)&lt;br&gt;
🔐 Content Security Policy (CSP)&lt;br&gt;
The goal of this series isn't to simply copy a list of security headers, but to understand what each header does, what problem it addresses, and how these controls fit into a real Angular deployment.&lt;/p&gt;
&lt;p&gt;This is Part 1, where we establish the foundation. The upcoming parts will move into the actual CSP, Nginx, and Docker configuration used to serve the application.&lt;/p&gt;
&lt;p&gt;📖 Read Part 1:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@jangidamit800/hardening-an-angular-application-understanding-security-headers-0c65644b981b&quot;&gt;https://medium.com/@jangidamit800/hardening-an-angular-application-understanding-security-headers-0c65644b981b&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Would love to hear how others approach security hardening for Angular applications.&lt;/p&gt;
&lt;p&gt; #Angular #AngularSecurity #WebSecurity #CSP #Nginx #DevSecOps #FrontendSecurity #WebDevelopment&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30585/angular-security-headers-a-practical-guide-to-securing-your-angular-application</guid>
<pubDate>Sat, 10 Oct 2026 12:37:43 +0000</pubDate>
</item>
<item>
<title>Same Ingress, Different Defaults: What Changed When We Moved to HAProxy</title>
<link>https://coderlegion.com/30239/same-ingress-different-defaults-what-changed-when-we-moved-to-haproxy</link>
<description>&lt;p&gt;Over the last few weeks, we moved the customer-facing managed services on our &lt;a rel=&quot;nofollow&quot; href=&quot;https://nine.ch/en/products/kubernetes/&quot;&gt;NKE&lt;/a&gt; service clusters and &lt;a rel=&quot;nofollow&quot; href=&quot;https://nine.ch/en/products/deploio/&quot;&gt;Deploio&lt;/a&gt; from ingress-nginx to HAProxy: Grafana, Loki, ArgoCD, the container registry, Alertmanager, Tempo, OpenSearch, the Object Storage endpoints and all Deploio applications. Some clusters alone had more than 200 Ingresses.&lt;/p&gt;
&lt;p&gt;We have written about the motivation twice before. &lt;a rel=&quot;nofollow&quot; href=&quot;https://nine.ch/en/blog/retirement-of-the-ingress-nginx-project/&quot;&gt;In December&lt;/a&gt;, we explained what the retirement of ingress-nginx meant and which migration paths were open. &lt;a rel=&quot;nofollow&quot; href=&quot;https://nine.ch/en/blog/techtalk-29-our-ingress-odyssey/&quot;&gt;Our TechTalk #29&lt;/a&gt; recap covered our evaluation of Traefik, Gateway API and HAProxy, and why HAProxy became our default for classic Ingress on NKE.&lt;/p&gt;
&lt;p&gt;This post is about what happened after that decision.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.nine.ch/docs/managed-kubernetes/nke/ingress&quot;&gt;annotation mapping table in our documentation&lt;/a&gt; gets you a working Ingress on HAProxy in a few minutes. What it does not show is how the defaults shift underneath your workloads. Two ingress controllers can parse the same Ingress resource and still differ in how they talk to your backends, which status codes they send, how long they wait and how many connections they accept before refusing new ones.&lt;/p&gt;
&lt;p&gt;We found most of these differences in production, after the switch. One of them broke a customer’s website asset delivery. We are sharing them so you can check your own setup before you migrate.&lt;/p&gt;
&lt;h3 id=&quot;http-to-https-redirects&quot;&gt;HTTP to HTTPS Redirects&lt;/h3&gt;
&lt;p&gt;While comparing responses from both controllers, we noticed that plain HTTP requests were redirected with 302 Found on HAProxy, whereas nginx returned 308 Permanent Redirect.&lt;/p&gt;
&lt;p&gt;ingress-nginx sets http-redirect-code to 308 by default. haproxy-ingress defaults its corresponding ssl-redirect-code to a temporary 302. Browsers follow both, but API clients, search crawlers and intermediate caches treat them differently. A 302 allows clients to rewrite a POST into a GET on redirect, whereas a 308 preserves the request method and body.&lt;/p&gt;
&lt;p&gt;A silent downgrade to temporary redirects can break API consumers, so we set ssl-redirect-code: &quot;308&quot; globally on our managed controllers to keep the previous behaviour.&lt;/p&gt;
&lt;h3 id=&quot;connection-limits&quot;&gt;Connection Limits&lt;/h3&gt;
&lt;p&gt;During peak traffic, requests to Deploio applications slowed down and some new connections were refused.&lt;/p&gt;
&lt;p&gt;The cause was the connection limit. nginx and HAProxy calculate their ceilings differently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx multiplies worker_processes by worker_connections. With&lt;br&gt;
ingress-nginx setting worker_processes to the node’s CPU count and&lt;br&gt;
worker_connections to 16384, even a modest node can handle tens of&lt;br&gt;
thousands of concurrent connections.&lt;/li&gt;
&lt;li&gt;HAProxy enforces a single global maxconn, which haproxy-ingress&lt;br&gt;
exposes as max-connections and sets to 2,000 per pod by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once a pod reaches that ceiling, HAProxy stops accepting new connections. They then queue in the kernel’s accept backlog, which caused the slowdown. Once the backlog is full, they are dropped.&lt;/p&gt;
&lt;p&gt;We raised max-connections to 20,000 across all managed controllers.&lt;/p&gt;
&lt;h3 id=&quot;timeouts&quot;&gt;Timeouts&lt;/h3&gt;
&lt;p&gt;Shortly after we migrated Deploio, some customers saw requests fail with 504 Gateway Timeout.&lt;/p&gt;
&lt;p&gt;haproxy-ingress defaults timeout-client and timeout-server to 50 seconds, while ingress-nginx waits 60 seconds. Ten seconds make no difference for typical web requests, but data exports or reports that reliably finished in 55 seconds suddenly failed with a 504.&lt;/p&gt;
&lt;p&gt;To avoid cutting off requests that used to succeed, our managed controllers set both timeout-client and timeout-server to 60 seconds. You can still override the timeout per Ingress with haproxy-ingress.github.io/timeout-server in place of nginx.ingress.kubernetes.io/proxy-read-timeout.&lt;/p&gt;
&lt;h3 id=&quot;upstream-http-version&quot;&gt;Upstream HTTP Version&lt;/h3&gt;
&lt;p&gt;In July, we moved an S3-compatible &lt;a rel=&quot;nofollow&quot; href=&quot;https://nine.ch/en/products/objectstorage/&quot;&gt;Object Storage&lt;/a&gt; endpoint to HAProxy. A customer served website assets through their own nginx reverse proxy in front of that endpoint. Right after the cutover, every asset request failed with 400 Bad Request.&lt;/p&gt;
&lt;p&gt;The cause: nginx’s proxy_pass talks to upstreams over HTTP/1.0 by default. ingress-nginx always spoke HTTP/1.1 to backends, regardless of the client’s protocol, and so it silently upgraded the customer’s HTTP/1.0 requests. HAProxy forwards the request with the protocol version the client sent. The Object Storage backend rejected HTTP/1.0 requests, which produced the 400 errors.&lt;/p&gt;
&lt;p&gt;The fix was for the customer to add proxy_http_version 1.1 to their reverse proxy configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location / {
    proxy_pass http://storage-endpoint;
    proxy_http_version 1.1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run a reverse proxy in front of an NKE or Deploio Ingress, check which HTTP version it sends.&lt;/p&gt;
&lt;h3 id=&quot;basic-auth&quot;&gt;Basic Auth&lt;/h3&gt;
&lt;p&gt;After we had migrated most managed services, one of the controllers spiked to more than two full CPU cores while serving only 155 concurrent connections. Almost all traffic to that pod hit two endpoints protected by HTTP basic authentication, one of them our metrics ingestion pipeline.&lt;/p&gt;
&lt;p&gt;HAProxy verifies basic authentication via crypt(3) synchronously on every request, without caching. A bcrypt hash with a cost factor of 10, the default in most standard libraries and in Helm’s htpasswd template function, takes about 50 ms of CPU time to verify. At 50 ms per check, twenty requests per second are enough to saturate a whole CPU core.&lt;/p&gt;
&lt;p&gt;This also applies to nginx, as its auth_basic module verifies every request synchronously and caches nothing either. Because each nginx worker is a single-threaded event loop, a 50 ms verification stalls every connection on that worker. But workers are separate processes, so a controller with eight workers can verify eight passwords in parallel.&lt;/p&gt;
&lt;p&gt;HAProxy uses the thread-safe crypt_r(3) only on glibc and FreeBSD. On every other platform, including the Alpine-based image that haproxy-ingress ships, it wraps the plain crypt(3) call in a single process-wide spinlock. Only one thread can verify a password at a time, which caps the whole pod at roughly twenty verifications per second, no matter how many cores it has. And because it is a spinlock, waiting threads do not sleep. They keep their cores busy until it is their turn. That is how a pod serving 155 connections ended up using more than two cores.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.haproxy.org/3.0/configuration.html#3.4-user&quot;&gt;HAProxy manual&lt;/a&gt; warns that hashed passwords “can quickly become a major factor in HAProxy’s overall CPU consumption” and notes that musl’s hash implementations are slower than glibc’s. An upstream request to cache successful basic authentication results has been open since 2019 in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/haproxy/haproxy/issues/370&quot;&gt;haproxy/haproxy#370&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For credentials we generate internally, we reduced the bcrypt cost to 4, where verification takes under one millisecond. This is safe because those passwords are long, machine-generated strings of 20 random characters, which makes brute-force attacks computationally infeasible regardless of the cost factor. Helm’s built-in htpasswd function always hashes with bcrypt’s default cost of 10 and offers no way to change it (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Masterminds/sprig/blob/v3.3.0/crypto.go#L61&quot;&gt;Sprig source&lt;/a&gt;), so chart-generated credentials need to be hashed outside Helm.&lt;/p&gt;
&lt;p&gt;The annotation syntax is simpler on HAProxy. Setting haproxy-ingress.github.io/auth-secret alone enables authentication, auth-type is deprecated. The secret format stays the same: an htpasswd file stored under the auth key.&lt;/p&gt;
&lt;h3 id=&quot;x-forwarded-for&quot;&gt;X-Forwarded-For&lt;/h3&gt;
&lt;p&gt;On paper, this looks like a change in behaviour. In practice it is not one, but it does add a header.&lt;/p&gt;
&lt;p&gt;ingress-nginx replaced any incoming X-Forwarded-For with the connecting IP unless the cluster-wide compute-full-forwarded-for option was enabled, and on our shared controllers it was off.&lt;/p&gt;
&lt;p&gt;HAProxy’s option forwardfor on its own behaves differently: it appends the connecting IP, so if the client already sent an X-Forwarded-For, the backend receives both. Applications behind a CDN or reverse proxy would then see the whole proxy chain instead of the single value ingress-nginx gave them.&lt;/p&gt;
&lt;p&gt;haproxy-ingress closes that gap. Its forwardfor key defaults to add, which deletes any incoming X-Forwarded-For before HAProxy inserts the connecting IP. Your application therefore sees the same as before: a single address, the one that connected directly to the ingress, for example your CDN.&lt;/p&gt;
&lt;p&gt;What is new is one extra header. If the request already carried an X-Forwarded-For, haproxy-ingress copies its original value to X-Original-Forwarded-For before discarding it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;X-Forwarded-For: &amp;lt;cdn-or-proxy-ip&amp;gt;
X-Original-Forwarded-For: &amp;lt;whatever-the-client-sent&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On its own, X-Original-Forwarded-For is untrusted input. It is copied verbatim from whoever connected to the ingress, and anyone can put anything into it.&lt;/p&gt;
&lt;p&gt;It becomes useful once your application checks who connected. X-Forwarded-For holds the address that opened the TCP connection to HAProxy, which the client cannot forge. If that address belongs to a CDN you trust, for example one of the published edge IPs of your provider, then X-Original-Forwarded-For is the header that CDN sent. Its rightmost entry is the address the CDN saw connecting to it.&lt;/p&gt;
&lt;h3 id=&quot;hsts-headers&quot;&gt;HSTS Headers&lt;/h3&gt;
&lt;p&gt;haproxy-ingress defaults its Strict-Transport-Security header to max-age=15768000 (six months). ingress-nginx sent max-age=31536000 (one year).&lt;/p&gt;
&lt;p&gt;RFC 6797 leaves the duration to the operator, and the two projects measure against different yardsticks. Six months is the minimum &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide&quot;&gt;Qualys SSL Labs&lt;/a&gt; has required for an A+ grade since January 2014. HAProxy’s own &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.haproxy.com/blog/haproxy-and-http-strict-transport-security-hsts&quot;&gt;documentation examples&lt;/a&gt; use a value just above it, max-age=16000000, described as “a bit more than 6 months”. When HAProxy published those examples, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://hstspreload.org/&quot;&gt;HSTS preload list&lt;/a&gt; accepted a max-age shorter than one year. It has since raised the minimum to one year.&lt;/p&gt;
&lt;p&gt;For standard web traffic, this has no functional impact. If your domain is on the HSTS preload list, however, six months fails validation. We therefore override the haproxy-ingress default and send max-age=31536000 (one year), the same value ingress-nginx did.&lt;/p&gt;
&lt;p&gt;If you need a different duration, set spec.forProvider.hsts.maxAge on the IngressHAProxy resource (a duration, for example 8760h). For a single Ingress, set haproxy-ingress.github.io/hsts-max-age (in seconds).&lt;/p&gt;
&lt;h3 id=&quot;request-buffering&quot;&gt;Request Buffering&lt;/h3&gt;
&lt;p&gt;Under ingress-nginx, high-volume write endpoints produced warnings like this in the controller logs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[warn] a client request body is buffered to a temporary file /tmp/nginx/client-body/0000000123
  request: &quot;POST /loki/api/v1/push HTTP/1.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A while back, one node in our service cluster ran at 98% CPU, with its nginx pod using 1.4 cores mainly to write and clean up temporary disk buffers. On nginx, the fix was to set nginx.ingress.kubernetes.io/proxy-request-buffering: &quot;off&quot; on write-heavy Ingresses such as Loki, Promscale and container registries.&lt;/p&gt;
&lt;p&gt;HAProxy streams request bodies to backends by default. There is no equivalent buffering annotation, because HAProxy does not write incoming payloads to disk.&lt;/p&gt;
&lt;h3 id=&quot;where-this-leaves-you&quot;&gt;Where This Leaves You&lt;/h3&gt;
&lt;p&gt;If you still use our managed ingress-nginx on NKE: as announced, support ended on 1 October 2026. HAProxy is available for every NKE cluster, and our &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.nine.ch/docs/managed-kubernetes/nke/ingress&quot;&gt;migration documentation&lt;/a&gt; is the place to start.&lt;/p&gt;
&lt;p&gt;If you are unsure how one of these differences affects your workloads, write to us at &lt;em&gt;Emails are not allowed&lt;/em&gt; and we will look at it with you.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30239/same-ingress-different-defaults-what-changed-when-we-moved-to-haproxy</guid>
<pubDate>Thu, 08 Oct 2026 08:23:47 +0000</pubDate>
</item>
<item>
<title>How to Run SafeLine WAF Behind an Nginx Reverse Proxy</title>
<link>https://coderlegion.com/27780/how-to-run-safeline-waf-behind-an-nginx-reverse-proxy</link>
<description>&lt;h2 id=&quot;how-to-run-safeline-waf-behind-an-nginx-reverse-proxy&quot;&gt;How to Run SafeLine WAF Behind an Nginx Reverse Proxy&lt;/h2&gt;
&lt;p&gt;Sometimes Nginx is already your public edge — terminating TLS, serving static files, doing virtual hosting. You can keep Nginx exactly where it is and place SafeLine &lt;strong&gt;behind&lt;/strong&gt; it, so Nginx forwards traffic to SafeLine for inspection before it reaches your app.&lt;/p&gt;
&lt;h3 id=&quot;the-topology&quot;&gt;The topology&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Client → Nginx (edge / TLS) → SafeLine (inspects) → upstream app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nginx stays the entry point. Instead of proxying straight to your app, it proxies to SafeLine; SafeLine then proxies to the real upstream. You get Nginx's edge features plus SafeLine's WAF filtering.&lt;/p&gt;
&lt;h3 id=&quot;step-1-install-safeline&quot;&gt;Step 1 — Install SafeLine&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-tell-safeline-where-the-app-is&quot;&gt;Step 2 — Tell SafeLine where the app is&lt;/h3&gt;
&lt;p&gt;In the SafeLine console (&lt;code&gt;https://&amp;lt;safeline-ip&amp;gt;:9443&lt;/code&gt;), add a protected site whose &lt;strong&gt;upstream&lt;/strong&gt; is your actual app, e.g. &lt;code&gt;http://127.0.0.1:3000&lt;/code&gt; or &lt;code&gt;http://10.0.0.10:8080&lt;/code&gt;. SafeLine will listen on its own port (default :80) and forward clean traffic there.&lt;/p&gt;
&lt;h3 id=&quot;step-3-point-nginx-at-safeline&quot;&gt;Step 3 — Point Nginx at SafeLine&lt;/h3&gt;
&lt;p&gt;Change Nginx's &lt;code&gt;proxy_pass&lt;/code&gt; from the app to SafeLine. A minimal upstream looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;upstream safeline {
    server 127.0.0.1:80;   # SafeLine's listener
}

server {
    listen 443 ssl;
    server_name app.example.com;

    location / {
        proxy_pass http://safeline;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload Nginx. Traffic now flows Client → Nginx → SafeLine → app.&lt;/p&gt;
&lt;h3 id=&quot;step-4-preserve-the-real-client-ip&quot;&gt;Step 4 — Preserve the real client IP&lt;/h3&gt;
&lt;p&gt;Because Nginx proxies to SafeLine, SafeLine sees Nginx's address by default. Pass &lt;code&gt;X-Forwarded-For&lt;/code&gt; (above) and configure SafeLine's trusted-proxy / client-IP setting so its logs show the real visitor IP rather than Nginx's.&lt;/p&gt;
&lt;h3 id=&quot;faq&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;why-put-safeline-behind-nginx-instead-of-in-front-&quot;&gt;Why put SafeLine behind Nginx instead of in front?&lt;/h4&gt;
&lt;p&gt;If Nginx already handles TLS, redirects, or static assets you don't want to move, keeping it at the edge and forwarding to SafeLine is the least-disruptive way to add a WAF.&lt;/p&gt;
&lt;h4 id=&quot;can-safeline-also-do-tls-&quot;&gt;Can SafeLine also do TLS?&lt;/h4&gt;
&lt;p&gt;Yes — you can terminate TLS at SafeLine instead. Behind-Nginx just describes the case where Nginx keeps that job.&lt;/p&gt;
&lt;h4 id=&quot;does-this-affect-performance-much-&quot;&gt;Does this affect performance much?&lt;/h4&gt;
&lt;p&gt;SafeLine adds one lightweight proxy hop; for most sites the latency is negligible.&lt;/p&gt;
&lt;h4 id=&quot;free-tier-&quot;&gt;Free tier?&lt;/h4&gt;
&lt;p&gt;The Community Edition covers 10 apps at 800 QPS for free.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;That's it — Nginx stays your edge, and SafeLine quietly filters everything behind it.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⭐ &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;SafeLine WAF on GitHub&lt;/a&gt; — give it a star if you find it useful&lt;/li&gt;
&lt;li&gt;🔗 &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deploy it in minutes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27780/how-to-run-safeline-waf-behind-an-nginx-reverse-proxy</guid>
<pubDate>Sun, 20 Sep 2026 01:44:17 +0000</pubDate>
</item>
<item>
<title>SafeLine WAF vs Nginx WAF: Why a Dedicated Engine Beats a Module</title>
<link>https://coderlegion.com/26769/safeline-waf-vs-nginx-waf-why-a-dedicated-engine-beats-a-module</link>
<description>&lt;h2 id=&quot;safeline-waf-vs-nginx-waf-why-a-dedicated-engine-beats-a-module&quot;&gt;SafeLine WAF vs Nginx WAF: Why a Dedicated Engine Beats a Module&lt;/h2&gt;
&lt;p&gt;&quot;Nginx WAF&quot; usually means either the commercial NGINX WAF module or ModSecurity&lt;br&gt;
bolted onto nginx. SafeLine is a standalone, self-hosted WAF you place in front of nginx. The&lt;br&gt;
difference is where the intelligence lives.&lt;/p&gt;
&lt;h3 id=&quot;the-nginx-approach&quot;&gt;The nginx approach&lt;/h3&gt;
&lt;p&gt;nginx itself is a great reverse proxy and can host a WAF module. The commercial NGINX WAF and the&lt;br&gt;
ModSecurity/CRS combo both work, but they inherit the same tuning burden: you manage rules,&lt;br&gt;
thresholds, and false positives.&lt;/p&gt;
&lt;h3 id=&quot;safeline-in-front-of-nginx&quot;&gt;SafeLine in front of nginx&lt;/h3&gt;
&lt;p&gt;SafeLine sits as a reverse proxy before your nginx upstream. Requests are inspected by its&lt;br&gt;
semantic-analysis engine before they ever reach your app server. Because detection is based on&lt;br&gt;
request intent rather than a signature list, you spend less time writing exception rules.&lt;/p&gt;
&lt;h3 id=&quot;why-a-dedicated-engine-helps&quot;&gt;Why a dedicated engine helps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clean separation&lt;/strong&gt;: WAF logic lives in its own container; your nginx config stays about&lt;br&gt;
routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Central dashboard&lt;/strong&gt;: SafeLine gives you a web UI at &lt;code&gt;https://&amp;lt;ip&amp;gt;:9443&lt;/code&gt; for policies, logs,&lt;br&gt;
and alerts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower false positives&lt;/strong&gt;: semantic analysis is gentler on legitimate, unusual traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;faq-1&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;do-i-have-to-remove-nginx-&quot;&gt;Do I have to remove nginx?&lt;/h4&gt;
&lt;p&gt;No. Keep nginx for routing; put SafeLine in front as the inspection layer.&lt;/p&gt;
&lt;h4 id=&quot;can-i-run-it-on-the-same-host-&quot;&gt;Can I run it on the same host?&lt;/h4&gt;
&lt;p&gt;Yes, as a container sharing the host network or a dedicated port.&lt;/p&gt;
&lt;h4 id=&quot;is-it-free-&quot;&gt;Is it free?&lt;/h4&gt;
&lt;p&gt;The Community Edition is free for 10 apps at 800 QPS.&lt;/p&gt;
&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h3&gt;
&lt;p&gt;If you want a &lt;strong&gt;self-hosted WAF&lt;/strong&gt; you fully control, SafeLine is worth a look. It sits in&lt;br&gt;
front of your existing stack as a container, uses a semantic-analysis engine to catch SQL&lt;br&gt;
injection, XSS, and bot abuse with very low false positives, and the &lt;strong&gt;Community Edition is&lt;br&gt;
free&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deploy it in minutes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26769/safeline-waf-vs-nginx-waf-why-a-dedicated-engine-beats-a-module</guid>
<pubDate>Fri, 11 Sep 2026 02:39:56 +0000</pubDate>
</item>
<item>
<title>How to Configure Nginx as an HTTPS Proxy Server?</title>
<link>https://coderlegion.com/18996/how-to-configure-nginx-as-an-https-proxy-server</link>
<description>
&lt;p&gt;Hello, I'm Ganesh. I'm building &lt;em&gt;git-lrc&lt;/em&gt;, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Star Us&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback for improving the product.&lt;/p&gt;

&lt;p&gt;In previous article we could able to setup basic nginx server and server simple html page. &lt;/p&gt;

&lt;p&gt;Now let's setup https for our nginx server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Before we start setting up https we should do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buy a domain name from any registrar.&lt;/li&gt;
&lt;li&gt;Set up dns records to point to our server ip.&lt;/li&gt;
&lt;li&gt;Setup Certificate for our domain name.&lt;/li&gt;
&lt;li&gt;Install nginx on our server.&lt;/li&gt;
&lt;li&gt;Set up nginx server to serve our website.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Buying Domain Name
&lt;/h2&gt;

&lt;p&gt;We can buy domain name from any registrar like Namecheap, GoDaddy, etc. &lt;/p&gt;

&lt;p&gt;It depends on which name it is and the charges will be around &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up DNS Records
&lt;/h2&gt;

&lt;p&gt;Once buy setup the dns records to point to our server ip.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
For your server the ip address is [IP_ADDRESS]. &lt;br&gt;
And your domain name is [EMAIL_ADDRESS]&lt;/p&gt;

&lt;p&gt;So, you need to set up a dns record to point to your server ip.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Certificate
&lt;/h2&gt;

&lt;p&gt;Now let's setup certificate for our domain name.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;example.com&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;so, we setup&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot &lt;span class=&quot;nt&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;example.com&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;EMAIL_ADDRESS] &lt;span class=&quot;nt&quot;&gt;--agree-tos&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-eff-email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is simple workflow on how certificate is fetched and how &lt;/p&gt;

&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftruvgc7r97ocn2ggu19.png&quot; class=&quot;article-body-image-wrapper&quot;&gt;&lt;img src=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftruvgc7r97ocn2ggu19.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key generation&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The Certbot generates a private key and a CSR (Certificate Signing Request) entirely on your machine. &lt;/p&gt;

&lt;p&gt;The private key is the core security guarantee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP-01 Challenge&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Let's Encrypt needs to verify you actually control &lt;code&gt;example.com&lt;/code&gt;. It sends Certbot a random token. &lt;/p&gt;

&lt;p&gt;Certbot places it at:&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight plaintext&quot;&gt;&lt;code&gt;/var/www/html/.well-known/acme-challenge/&amp;lt;random-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx (already running on port 80) serves this file publicly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's Encrypt fetches the token&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;LE makes a plain HTTP request to &lt;code&gt;&lt;a href=&quot;http://example.com/.well-known/acme-challenge/&quot; rel=&quot;nofollow&quot;&gt;http://example.com/.well-known/acme-challenge/&lt;/a&gt;&amp;lt;token&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If it gets the right response back, it's satisfied that you own the domain. &lt;/p&gt;

&lt;p&gt;This is why DNS must be pointing to your server before you run Certbot &amp;mdash; if the domain pointed elsewhere, LE would fetch the token from the wrong machine and the challenge would fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificate issued &amp;amp; saved&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;LE signs and returns your certificate. &lt;/p&gt;

&lt;p&gt;Certbot saves four files to &lt;code&gt;/etc/letsencrypt/live/example.com/&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;table-wrapper-paragraph&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fullchain.pem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your cert + intermediate CA chain (this is what Nginx uses)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;privkey.pem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your private key (Nginx uses this too)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cert.pem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Just your cert alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chain.pem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Just the CA chain alone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Nginx config is rewritten&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Certbot patches your &lt;code&gt;server&lt;/code&gt; block to add the &lt;code&gt;listen 443 ssl&lt;/code&gt; lines and the cert paths, and adds a new &lt;code&gt;server { listen 80; }&lt;/code&gt; block that redirects all HTTP traffic to HTTPS. &lt;/p&gt;

&lt;p&gt;Then it reloads Nginx for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up with nginx
&lt;/h2&gt;

&lt;p&gt;Assuming your application running in local host 8090. and you are setting up https for your domain name example.com.&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight conf&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; {
    &lt;span class=&quot;n&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;443&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ssl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http2&lt;/span&gt;;
    &lt;span class=&quot;n&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;;

    &lt;span class=&quot;n&quot;&gt;ssl_certificate&lt;/span&gt;     /&lt;span class=&quot;n&quot;&gt;etc&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;letsencrypt&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;live&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;fullchain&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;pem&lt;/span&gt;;
    &lt;span class=&quot;n&quot;&gt;ssl_certificate_key&lt;/span&gt; /&lt;span class=&quot;n&quot;&gt;etc&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;letsencrypt&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;live&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;privkey&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;pem&lt;/span&gt;;

    &lt;span class=&quot;c&quot;&gt;# All requests &amp;mdash; public, no auth
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;location&lt;/span&gt; / {
        &lt;span class=&quot;n&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;://&lt;span class=&quot;n&quot;&gt;localhost&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;8090&lt;/span&gt;;
        &lt;span class=&quot;n&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt; $&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;;
        &lt;span class=&quot;n&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;Real&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;IP&lt;/span&gt; $&lt;span class=&quot;n&quot;&gt;remote_addr&lt;/span&gt;;
        &lt;span class=&quot;n&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;Forwarded&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;For&lt;/span&gt; $&lt;span class=&quot;n&quot;&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
        &lt;span class=&quot;n&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;Forwarded&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;Proto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to symlink to sites enabled&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restarting nginx&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We could get https for our domain name example.com.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;&lt;img src=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabn6nl3iehrohehnxgje.png&quot; alt=&quot;git-lrc&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It&amp;rsquo;s online, source-available, and ready for anyone to use. &lt;br&gt;
&amp;#11088; Star it on GitHub: &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;https://github.com/HexmosTech/git-lrc&lt;/a&gt;&lt;/p&gt;


</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/18996/how-to-configure-nginx-as-an-https-proxy-server</guid>
<pubDate>Tue, 02 Jun 2026 16:03:57 +0000</pubDate>
</item>
<item>
<title>Developer Journal..“My First Docker + Nginx Setup on Ubuntu”</title>
<link>https://coderlegion.com/18088/developer-journal-my-first-docker-nginx-setup-on-ubuntu</link>
<description>&lt;p&gt;Hello Everyone!&lt;/p&gt;
&lt;p&gt;A while ago, I decided to learn Docker and, honestly, it turned out to be much simpler than I expected. In this article, I’ll walk you through how I configured Docker on Windows using WSL (Windows Subsystem for Linux) and ran my very first container.&lt;/p&gt;
&lt;p&gt;If you're a developer using Windows and want to get into Docker, this post is for you.&lt;/p&gt;
&lt;p&gt;Spoiler: At the end, you’ll see the Nginx welcome page in your browser — and that feeling is amazing &lt;/p&gt;
&lt;p&gt;What is Docker? (In Simple Terms)&lt;/p&gt;
&lt;p&gt;Docker is a platform that allows you to package your application inside a “container” — an isolated and portable environment that behaves the same way on any machine.&lt;/p&gt;
&lt;p&gt;Think of it as a sealed box containing everything your app needs to run.&lt;/p&gt;
&lt;p&gt;Prerequisites&lt;/p&gt;
&lt;p&gt;Before getting started, you’ll need:&lt;/p&gt;
&lt;p&gt;✅ Windows 10 or 11&lt;br&gt;
✅ WSL (Windows Subsystem for Linux) enabled&lt;br&gt;
✅ A Linux distribution installed (we’ll use Ubuntu)&lt;br&gt;
✅ PowerShell running as Administrator&lt;br&gt;
Step 1: Verify and Install WSL&lt;/p&gt;
&lt;p&gt;WSL allows you to run Linux commands directly on Windows. Let’s first verify whether it’s installed.&lt;/p&gt;
&lt;p&gt;Option A: List all available distributions&lt;br&gt;
wsl --list --online&lt;/p&gt;
&lt;p&gt;This command will show all Linux distributions available for installation.&lt;/p&gt;
&lt;p&gt;Option B: Install Ubuntu on WSL (if you don’t already have it)&lt;br&gt;
wsl --install -d Ubuntu&lt;br&gt;
Option C: Check installed distributions&lt;br&gt;
wsl --list --verbose&lt;/p&gt;
&lt;p&gt;This command is very useful because it shows the state and version of each installed distribution.&lt;/p&gt;
&lt;p&gt;Step 2: Open Ubuntu in WSL&lt;/p&gt;
&lt;p&gt;From PowerShell, type:&lt;/p&gt;
&lt;p&gt;wsl -d Ubuntu&lt;/p&gt;
&lt;p&gt;You should see something like:&lt;/p&gt;
&lt;p&gt;user@PC:~$&lt;/p&gt;
&lt;p&gt;Congratulations! You are now inside Ubuntu. From this point on, all commands will be Linux commands.&lt;/p&gt;
&lt;p&gt;Step 3: Verify and Install Docker&lt;/p&gt;
&lt;p&gt;First, let’s check if Docker is already installed:&lt;/p&gt;
&lt;p&gt;docker --version&lt;/p&gt;
&lt;p&gt;If Docker is not installed, you’ll see a message suggesting available packages. Install it with:&lt;/p&gt;
&lt;p&gt;sudo apt-get install docker.io -y&lt;/p&gt;
&lt;p&gt;The -y flag automatically answers “yes” to confirmation prompts, saving time.&lt;/p&gt;
&lt;p&gt;Verify the installation&lt;br&gt;
docker --version&lt;/p&gt;
&lt;p&gt;You should see something like:&lt;/p&gt;
&lt;p&gt;Docker version 24.0.x&lt;br&gt;
Step 4: Enable and Start the Docker Service&lt;/p&gt;
&lt;p&gt;Docker is now installed, but we still need to start the service.&lt;/p&gt;
&lt;p&gt;Enable Docker to start automatically&lt;br&gt;
sudo systemctl enable docker&lt;br&gt;
Start the service immediately&lt;br&gt;
sudo systemctl start docker&lt;br&gt;
Step 5: Fix Permission Errors&lt;/p&gt;
&lt;p&gt;If you encounter an error like this while running Docker commands:&lt;/p&gt;
&lt;p&gt;PERMISSION DENIED WHILE TRYING TO CONNECT TO THE DOCKER API AT UNIX:///VAR/RUN/DOCKER.SOCK&lt;/p&gt;
&lt;p&gt;Don’t worry! This is completely normal. Docker requires special permissions.&lt;/p&gt;
&lt;p&gt;Add your user to the Docker group:&lt;/p&gt;
&lt;p&gt;sudo usermod -aG docker your_user&lt;/p&gt;
&lt;p&gt;Replace your_user with your Linux username.&lt;/p&gt;
&lt;p&gt;Now you have two options:&lt;br&gt;
Option 1 (Recommended): Close and reopen Ubuntu&lt;br&gt;
exit&lt;/p&gt;
&lt;p&gt;Then reopen it from PowerShell:&lt;/p&gt;
&lt;p&gt;wsl -d Ubuntu&lt;br&gt;
Option 2: Activate the group immediately&lt;br&gt;
newgrp docker&lt;br&gt;
Verify everything works&lt;br&gt;
docker ps&lt;/p&gt;
&lt;p&gt;If you see an empty table with headers like CONTAINER ID, IMAGE, etc., Docker is working correctly &lt;/p&gt;
&lt;p&gt;Step 6: Run Your First Container (Nginx)&lt;/p&gt;
&lt;p&gt;Here comes the fun part. Let’s run Nginx, one of the most popular web servers:&lt;/p&gt;
&lt;p&gt;docker run -d -p 8080:80 nginx&lt;br&gt;
What does this command do?&lt;br&gt;
docker run → Runs a container&lt;br&gt;
-d → Runs it in detached mode (background)&lt;br&gt;
-p 8080:80 → Maps port 8080 on your machine to port 80 inside the container&lt;br&gt;
nginx → The image you want to run (Docker will download it automatically)&lt;/p&gt;
&lt;p&gt;You should see something similar to:&lt;/p&gt;
&lt;p&gt;Unable to find image 'nginx:latest' locally&lt;br&gt;
latest: Pulling from library/nginx&lt;br&gt;
...&lt;br&gt;
Digest: sha256:abc123...&lt;br&gt;
Status: Downloaded newer image for nginx:latest&lt;br&gt;
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&lt;/p&gt;
&lt;p&gt;That last string is your container ID.&lt;/p&gt;
&lt;p&gt;Your container is now running &lt;/p&gt;
&lt;p&gt;Step 7: Verify the Container is Running&lt;br&gt;
docker ps&lt;/p&gt;
&lt;p&gt;You should see your container listed with the status Up.&lt;/p&gt;
&lt;p&gt;Step 8: The Best Part — Open it in Your Browser&lt;/p&gt;
&lt;p&gt;Open any browser on Windows (Chrome, Firefox, Edge, etc.) and go to:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If everything worked correctly, you’ll see the beautiful Nginx welcome page saying:&lt;/p&gt;
&lt;p&gt;“Welcome to nginx!”&lt;/p&gt;
&lt;p&gt;That feeling when you realize everything is actually working is incredible &lt;/p&gt;
&lt;p&gt;Additional Useful Commands&lt;/p&gt;
&lt;p&gt;Here are a few Docker commands you’ll probably want to know:&lt;/p&gt;
&lt;p&gt;View all containers (including stopped ones)&lt;br&gt;
docker ps -a&lt;br&gt;
Stop a container&lt;br&gt;
docker stop CONTAINER_ID&lt;br&gt;
Remove a container&lt;br&gt;
docker rm CONTAINER_ID&lt;br&gt;
View container logs&lt;br&gt;
docker logs CONTAINER_ID&lt;br&gt;
Lessons Learned&lt;/p&gt;
&lt;p&gt;✅ WSL is essential: Without WSL, Docker on Windows can feel complicated. With WSL, the experience becomes much smoother.&lt;/p&gt;
&lt;p&gt;✅ Permissions matter: “Permission Denied” errors are completely normal. It’s not a broken installation — you just need the correct user group permissions.&lt;/p&gt;
&lt;p&gt;✅ Docker is beginner-friendly: The Docker ecosystem has done an excellent job making containerization accessible.&lt;/p&gt;
&lt;p&gt;✅ Documentation is your best friend: If something breaks, check the logs using docker logs.&lt;/p&gt;
&lt;p&gt;What’s Next?&lt;/p&gt;
&lt;p&gt;Now that Docker is working, you can:&lt;/p&gt;
&lt;p&gt;Build your own Dockerfile for a personal application&lt;br&gt;
Explore Docker Hub and discover thousands of pre-configured images&lt;br&gt;
Learn Docker Compose for multi-container orchestration&lt;br&gt;
Publish your own images on Docker Hub&lt;br&gt;
Final Thoughts&lt;/p&gt;
&lt;p&gt;My first experience with Docker on Windows was honestly great.&lt;/p&gt;
&lt;p&gt;The process is clear, well documented, and most importantly — you get immediate results. And that’s one of the best ways to learn.&lt;/p&gt;
&lt;p&gt;If you’re thinking about learning Docker:&lt;/p&gt;
&lt;p&gt;Don’t wait any longer.&lt;/p&gt;
&lt;p&gt;The journey into containerization begins with a simple:&lt;/p&gt;
&lt;p&gt;docker run&lt;/p&gt;
&lt;p&gt;Have questions? Ran into problems? Share your experience in the comments — I’d love to hear about your first Docker setup too &lt;/p&gt;
&lt;p&gt;References &amp;amp; Resources&lt;br&gt;
Docker Official Documentation&lt;br&gt;
WSL Documentation&lt;br&gt;
Docker Hub&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18088/developer-journal-my-first-docker-nginx-setup-on-ubuntu</guid>
<pubDate>Sat, 16 May 2026 14:24:42 +0000</pubDate>
</item>
<item>
<title>How to Configure and Control Nginx?</title>
<link>https://coderlegion.com/14386/how-to-configure-and-control-nginx</link>
<description>
&lt;p&gt;Hello, I'm Ganesh. I'm building &lt;em&gt;git-lrc&lt;/em&gt;, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Star Us&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback for improving the product.&lt;/p&gt;

&lt;p&gt;In previous article I explinaed how nginx works and what is the architecture of nginx.&lt;/p&gt;

&lt;p&gt;Now let's configure it and controls nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Nginx Controls
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reload
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This signal is used to reload the configuration files of nginx. &lt;br&gt;
It will not stop the nginx processes. &lt;br&gt;
It will just reload the configuration files and start the new worker processes.&lt;br&gt;
If old worker process serving few clients it will be served by old worker process.&lt;/p&gt;

&lt;p&gt;Either master will stop old worker process or it will wait for the old worker process to finish serving the current requests. &lt;/p&gt;

&lt;p&gt;This way if anything has issues nginx will not apply new configuration files.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Quit
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This signal is used to quit the nginx processes. &lt;/p&gt;

&lt;p&gt;It will stop the nginx processes and wait for the worker processes to finish serving the current requests.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This signal is used to stop the nginx processes.&lt;/p&gt;

&lt;p&gt;It will stop the nginx processes and wait for the worker processes to finish serving the current requests.&lt;/p&gt;

&lt;p&gt;To stop nginx with PID. We can use kill command.&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-QUIT&lt;/span&gt; &amp;lt;master_pid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also find all PID&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;ps &lt;span class=&quot;nt&quot;&gt;-ax&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Nginx
&lt;/h2&gt;

&lt;p&gt;nginx consists of modules which are controlled by directives specified in the configuration file. &lt;/p&gt;

&lt;p&gt;These Directives are divided into simple directives and block directives.&lt;/p&gt;

&lt;p&gt;Similar to C language, simple directive consists of the name and parameters separated by spaces and ends with a semicolon (;). &lt;/p&gt;

&lt;p&gt;A block directive has the same structure as a simple directive, but instead of the semicolon it ends with a set of additional instructions surrounded by braces ({ and }). &lt;/p&gt;

&lt;p&gt;If a block directive can have other directives inside braces, it is called a context (examples: events, http, server, and location).&lt;/p&gt;

&lt;p&gt;Directives placed in the configuration file outside of any contexts are considered to be in the main context. The events and http directives reside in the main context, server in http, and location in server.&lt;/p&gt;

&lt;p&gt;The rest of a line after the # sign is considered a comment.&lt;/p&gt;

&lt;p&gt;Here is the simple example of nginx configuration file:&lt;/p&gt;

&lt;p&gt;Main context: worker_processes 1; &lt;/p&gt;

&lt;p&gt;events context: worker_connections 1024; &lt;/p&gt;

&lt;p&gt;http context: we define server context here.&lt;/p&gt;

&lt;p&gt;server context: In here we define this to listen port 80 which is http port.&lt;/p&gt;

&lt;p&gt;location context: In here we define the root directory of the website.&lt;/p&gt;

&lt;p&gt;So, any request coming to port 80 will be served by the worker process with the root directory of &lt;code&gt;/var/www/html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# This is a comment in the Main Context&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;worker_processes&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;c1&quot;&gt;# This is a simple directive inside the 'events' context&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;worker_connections&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;c1&quot;&gt;# We are now inside the 'http' context&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;c1&quot;&gt;# The 'server' context is nested inside 'http'&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

        &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
            &lt;span class=&quot;c1&quot;&gt;# The 'location' context is nested inside 'server'&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/var/www/html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We understood how to control nginx and how to configure nginx.&lt;/p&gt;

&lt;p&gt;So, this makes solid foundation for us to understand how work with nginx.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;&lt;img src=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabn6nl3iehrohehnxgje.png&quot; alt=&quot;git-lrc&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It&amp;rsquo;s online, source-available, and ready for anyone to use. &lt;br&gt;
&amp;#11088; Star it on GitHub: &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;https://github.com/HexmosTech/git-lrc&lt;/a&gt;&lt;/p&gt;


</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/14386/how-to-configure-and-control-nginx</guid>
<pubDate>Tue, 21 Apr 2026 02:19:50 +0000</pubDate>
</item>
<item>
<title>How Nginx Architecture is Designed and How They Work</title>
<link>https://coderlegion.com/14073/how-nginx-architecture-is-designed-and-how-they-work</link>
<description>
&lt;p&gt;Hello, I'm Ganesh. I'm building &lt;em&gt;git-lrc&lt;/em&gt;, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Star Us&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback for improving the product.&lt;/p&gt;

&lt;p&gt;In the previous article, We have seen how to setup nginx on ubuntu and enable the site. It was more into basic where anyone one who wants to configure domain or any other thing can follow it.&lt;/p&gt;

&lt;p&gt;Now in this article we will explore a how nginx works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Nginx Architecture and How they Works?
&lt;/h2&gt;

&lt;p&gt;Before we go into how nginx works, we need to understand the architecture of nginx.&lt;/p&gt;

&lt;p&gt;Nginx has two main components: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Master Process&lt;/li&gt;
&lt;li&gt;Worker Process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Master process is to read, evaluate configuration files and maintain worker process.&lt;/p&gt;

&lt;p&gt;Worker Process will do all the processing of client requests. &lt;/p&gt;

&lt;p&gt;Nginx works on principle of event-based and OS-dependent mechanisms to efficiently distribute requests among worker processes.&lt;/p&gt;

&lt;p&gt;The number of worker process can be configured in the nginx.conf file and this can also be automatically set based on the number of CPU cores available on the system.&lt;/p&gt;

&lt;p&gt;For more information on how to configure the number of worker process, please refer to the official documentation &lt;a href=&quot;https://nginx.org/en/docs/ngx_core_module.html#worker_processes&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Worker Processes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, the configuration file is named &lt;code&gt;nginx.conf&lt;/code&gt; and placed in the directory &lt;code&gt;/usr/local/nginx/conf&lt;/code&gt;, &lt;code&gt;/etc/nginx&lt;/code&gt;, or &lt;code&gt;/usr/local/etc/nginx&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start, Stop and Reload Nginx?
&lt;/h2&gt;

&lt;p&gt;To Control any operation of nginx, we need to use the executable file. &lt;/p&gt;

&lt;p&gt;This will be having this syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; signal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where signal may be one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stop &amp;mdash; fast shutdown&lt;/li&gt;
&lt;li&gt;quit &amp;mdash; graceful shutdown&lt;/li&gt;
&lt;li&gt;reload &amp;mdash; reloading the configuration file&lt;/li&gt;
&lt;li&gt;reopen &amp;mdash; reopening the log files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to stop nginx processes with waiting for the worker processes to finish serving current requests, the following command can be executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As of now we understand how Nginx architecture works and how to control Nginx. &lt;/p&gt;

&lt;p&gt;In the next article, we will go in-depth on how to use these signals to control Nginx.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;&lt;img src=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabn6nl3iehrohehnxgje.png&quot; alt=&quot;git-lrc&quot; width=&quot;800&quot; height=&quot;373&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It&amp;rsquo;s online, source-available, and ready for anyone to use. &lt;br&gt;
&amp;#11088; Star it on GitHub: &lt;a href=&quot;https://github.com/HexmosTech/git-lrc&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;https://github.com/HexmosTech/git-lrc&lt;/a&gt;&lt;/p&gt;


</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/14073/how-nginx-architecture-is-designed-and-how-they-work</guid>
<pubDate>Thu, 09 Apr 2026 02:36:18 +0000</pubDate>
</item>
<item>
<title>Beyond `chmod 755`: A Senior Engineer’s Guide to Debugging Nginx 403 Forbidden</title>
<link>https://coderlegion.com/13412/beyond-chmod-755-a-senior-engineers-guide-to-debugging-nginx-403-forbidden</link>
<description>&lt;p&gt;The &lt;code&gt;403 Forbidden&lt;/code&gt; error in Nginx is deceptively simple. At face value, it signals &quot;access denied.&quot; In practice, it is the result of a decision chain that spans filesystem permissions, process identity, kernel-level security controls, and upstream security layers.&lt;/p&gt;
&lt;p&gt;The common reflex—&lt;code&gt;chmod -R 777&lt;/code&gt;—removes friction by collapsing the permission model. It also destroys any meaningful security boundary. The correct approach is to treat a 403 as a &lt;strong&gt;diagnostic signal&lt;/strong&gt;, not a configuration annoyance.&lt;/p&gt;
&lt;p&gt;This guide breaks down the problem the way it actually manifests in production systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-foundation-filesystem-traversal-and-process-identity&quot;&gt;The Foundation: Filesystem Traversal and Process Identity&lt;/h2&gt;
&lt;p&gt;Nginx does not operate as root (beyond initial binding). It runs as a constrained user such as &lt;code&gt;www-data&lt;/code&gt; or &lt;code&gt;nginx&lt;/code&gt;. That user must be able to &lt;strong&gt;traverse the entire directory chain&lt;/strong&gt;, not just read the target file.&lt;/p&gt;
&lt;p&gt;Given a path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/var/www/app/public/index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The required condition is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; on the file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; on &lt;strong&gt;every parent directory&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A failure at any level results in a 403.&lt;/p&gt;
&lt;p&gt;To inspect this precisely:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;namei -om /var/www/app/public/index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Typical problematic output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-x--- root root www
drwx------ root root app
drwxr-xr-x root root public
-rw-r--r-- root root index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;/var/www/app&lt;/code&gt; is &lt;code&gt;700&lt;/code&gt;, which blocks traversal for the Nginx worker.&lt;/p&gt;
&lt;p&gt;Corrective action is not &quot;open everything,&quot; but align ownership and minimal permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chown -R www-data:www-data /var/www/app
chmod 755 /var/www/app
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-configuration-layer-index-resolution-and-access-semantics&quot;&gt;The Configuration Layer: Index Resolution and Access Semantics&lt;/h2&gt;
&lt;p&gt;Nginx does not assume behavior. If a request targets a directory, resolution depends on explicit configuration.&lt;/p&gt;
&lt;p&gt;Example request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /assets/ HTTP/1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If no index file exists and directory listing is disabled (default), Nginx returns 403.&lt;/p&gt;
&lt;p&gt;Relevant configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;location /assets/ {
    root /var/www/app/public;
    index index.html;
    autoindex off;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Failure modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;index&lt;/code&gt; file missing → 403  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;autoindex off&lt;/code&gt; → no fallback  &lt;/li&gt;
&lt;li&gt;incorrect &lt;code&gt;root&lt;/code&gt; or &lt;code&gt;alias&lt;/code&gt; → silent mismatch  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To verify effective resolution, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nginx -T | grep -A5 &quot;location /assets/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-kernel-layer-selinux-and-apparmor&quot;&gt;The Kernel Layer: SELinux and AppArmor&lt;/h2&gt;
&lt;p&gt;If permissions appear correct but 403 persists, the denial is often happening &lt;strong&gt;below Nginx&lt;/strong&gt;, enforced by Mandatory Access Control (MAC).&lt;/p&gt;
&lt;p&gt;On SELinux-enabled systems, file context is decisive.&lt;/p&gt;
&lt;p&gt;Incorrect context example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -Z /var/www/app/public
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;-rw-r--r-- user user unconfined_u:object_r:user_home_t:s0 index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected context:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;httpd_sys_content_t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chcon -R -t httpd_sys_content_t /var/www/app/public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or persistently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;semanage fcontext -a -t httpd_sys_content_t &quot;/var/www/app/public(/.*)?&quot;
restorecon -Rv /var/www/app/public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To confirm denial source:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ausearch -m avc -ts recent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If AppArmor is in use (Ubuntu), check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dmesg | grep DENIED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this layer, Nginx is functioning correctly. The kernel is rejecting the access.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-security-layer-waf-induced-403s&quot;&gt;The Security Layer: WAF-Induced 403s&lt;/h2&gt;
&lt;p&gt;In modern deployments, a 403 often originates &lt;strong&gt;before the request reaches Nginx logic&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Indicators:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only specific payloads trigger 403  &lt;/li&gt;
&lt;li&gt;Requests with SQL keywords, encodings, or long parameters fail  &lt;/li&gt;
&lt;li&gt;Static assets load normally  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /api/user?id=1 OR 1=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Traditional WAFs (e.g., ModSecurity with OWASP CRS) rely on regex-heavy rule sets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;SecRule ARGS &quot;(?i:(union select|sleep\())&quot; &quot;id:1001,deny,status:403&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High false positive rate  &lt;/li&gt;
&lt;li&gt;Poor explainability  &lt;/li&gt;
&lt;li&gt;Debugging requires rule tracing across multiple layers  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This leads to a common failure mode: engineers disable rules to restore functionality.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;moving-toward-deterministic-visibility&quot;&gt;Moving Toward Deterministic Visibility&lt;/h2&gt;
&lt;p&gt;The core issue with 403 debugging is not complexity—it is &lt;strong&gt;lack of attribution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You need to answer one question precisely:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Which layer denied the request?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A modern WAF such as &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://safepoint.cloud/landing/safeline&quot;&gt;SafeLine&lt;/a&gt;&lt;/strong&gt; changes the model from passive blocking to &lt;strong&gt;explicit classification&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of opaque rule triggers, it provides structured reasoning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;attack type (e.g., SQL injection, RCE pattern)&lt;/li&gt;
&lt;li&gt;confidence score  &lt;/li&gt;
&lt;li&gt;matched behavioral pattern  &lt;/li&gt;
&lt;li&gt;request context  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;client_ip&quot;: &quot;203.0.113.10&quot;,
  &quot;path&quot;: &quot;/api/user&quot;,
  &quot;attack_type&quot;: &quot;SQL_INJECTION&quot;,
  &quot;action&quot;: &quot;BLOCKED&quot;,
  &quot;confidence&quot;: 0.97
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This eliminates ambiguity between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;misconfigured &lt;code&gt;nginx.conf&lt;/code&gt;  &lt;/li&gt;
&lt;li&gt;filesystem permission failure  &lt;/li&gt;
&lt;li&gt;kernel-level denial  &lt;/li&gt;
&lt;li&gt;security-layer intervention  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;practical-debugging-order-production-grade-&quot;&gt;Practical Debugging Order (Production-Grade)&lt;/h2&gt;
&lt;p&gt;When encountering a 403, the fastest resolution path is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. Filesystem traversal (namei)
2. Nginx config resolution (root, index, alias)
3. MAC layer (SELinux / AppArmor)
4. Upstream security (WAF / rate limiting)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Skipping layers leads to misdiagnosis.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-observation&quot;&gt;Final Observation&lt;/h2&gt;
&lt;p&gt;A 403 is not an error in isolation. It is a &lt;strong&gt;policy decision&lt;/strong&gt; made somewhere in the request pipeline.&lt;/p&gt;
&lt;p&gt;Junior handling removes the policy.&lt;/p&gt;
&lt;p&gt;Senior handling identifies &lt;strong&gt;which policy fired and why&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The difference is whether your system remains secure after the fix.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13412/beyond-chmod-755-a-senior-engineers-guide-to-debugging-nginx-403-forbidden</guid>
<pubDate>Tue, 24 Mar 2026 08:02:06 +0000</pubDate>
</item>
<item>
<title>F5's AI-Powered WAF Stopped a ChatGPT-Generated Attack — Without a Single Signature</title>
<link>https://coderlegion.com/12904/f5s-ai-powered-waf-stopped-a-chatgpt-generated-attack-without-a-single-signature</link>
<description>&lt;p&gt;The attack took about 30 seconds to set up. Open ChatGPT. Ask it to generate a SQL injection payload. Copy the output. Paste it into a login form.&lt;/p&gt;
&lt;p&gt;The payload that came back wasn't a known attack signature. It was a SQL injection wrapped around JSON extraction — a combination specific enough that no static rule had seen it before. When &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/kunalanand/&quot;&gt;Kunal Anand&lt;/a&gt;, Chief Product Officer at F5, dropped it into a test finance application during the closing keynote at F5 AppWorld 2026, it worked. JSON data containing sensitive information echoed back immediately. The application was compromised in under a minute using a payload generated on demand by a consumer AI tool.&lt;/p&gt;
&lt;p&gt;Then Anand enabled F5's new AI-enhanced WAF layer, went back to the login form, and dropped the same payload in again.&lt;/p&gt;
&lt;p&gt;Blocked. No new signatures. No new definitions. No prior knowledge of that specific attack pattern is required.&lt;/p&gt;
&lt;p&gt;That demo was the clearest illustration of what F5 announced at AppWorld this week: a fundamentally different approach to web application security, built on a neural network trained from scratch on F5's own traffic data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-static-signatures-are-structurally-obsolete&quot;&gt;Why Static Signatures Are Structurally Obsolete&lt;/h3&gt;
&lt;p&gt;Web application firewalls have always worked on pattern matching. Signatures, regular expressions, and known attack strings — the WAF maintains a library of what bad traffic looks like and blocks anything that matches. It's a proven model with decades of operational history.&lt;/p&gt;
&lt;p&gt;The problem is that the model assumes a relatively stable attack landscape. New attack patterns emerge, security researchers identify them, vendors push signature updates, and defenders catch up. The cycle has latency, but it works—as long as attackers operate from a finite library of known techniques.&lt;/p&gt;
&lt;p&gt;AI changes that assumption. An attacker with access to a capable language model can generate novel payloads on demand, specifically designed to evade known signatures. The attack surface is no longer a finite library — it's the combinatorial space of everything a capable AI can produce. Static signatures, by definition, can't keep up with that.&lt;/p&gt;
&lt;p&gt;Anand was direct about the implications at AppWorld: &quot;These AI agents and technologies are able to generate brand new payloads on the fly. They can learn, they can build something new, and they can counter those static rules and those static definitions, rendering them structurally obsolete.&quot;&lt;/p&gt;
&lt;p&gt;If the attacker can learn in real time, the defense has to as well.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-f5-built&quot;&gt;What F5 Built&lt;/h3&gt;
&lt;p&gt;F5 didn't patch their existing WAF. They built a new layer on top of it.&lt;/p&gt;
&lt;p&gt;The foundation — signatures, pattern matching, attack indicators, risk intelligence — remains in place. F5 treated it as the base, not the ceiling. On top of that foundation, they trained a transformer model from scratch using their own traffic data. Not a model pulled from an open source repository. Not a general-purpose LLM fine-tuned for security. A purpose-built neural network trained on F5's proprietary dataset.&lt;/p&gt;
&lt;p&gt;The technical constraint they solved for is important: the model runs on CPUs. No GPUs required. That means it can operate at the edge, in resource-constrained environments, without blowing up latency budgets. Bringing AI inference into the live traffic path is viable only if it doesn't introduce meaningful latency. F5's model is efficient enough to do that.&lt;/p&gt;
&lt;p&gt;The result is a WAF that can evaluate traffic it has never seen before and make an accurate, real-time decision about whether it's malicious — without needing a signature for that specific attack.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-numbers&quot;&gt;The Numbers&lt;/h3&gt;
&lt;p&gt;F5 shared performance data from the production deployment of the AI-enhanced WAF in December. The results are concrete enough to matter:&lt;/p&gt;
&lt;p&gt;Ten zero-day attacks were caught in December alone — without prior signatures or definitions. The model identified them as malicious based on behavioral patterns rather than known signatures.&lt;/p&gt;
&lt;p&gt;False positive rate for new signatures dropped from 28% to 1%. That number deserves emphasis. A 28% false-positive rate for new signatures means more than a quarter of legitimate traffic is flagged as malicious when new attack patterns emerge. Getting that to 1% is the difference between a security control that creates operational chaos and one that actually works in production.&lt;/p&gt;
&lt;p&gt;Out-of-the-box accuracy for new signatures went from 64% to 98%. Day-one accuracy without tuning or customization — which is what most organizations actually deploy into — improved from barely acceptable to near-production-ready.&lt;/p&gt;
&lt;p&gt;These are not F5's internal benchmarks. Anand noted during the keynote that the numbers were independently verified by a third party.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;agentic-ransomware-the-threat-context&quot;&gt;Agentic Ransomware: The Threat Context&lt;/h3&gt;
&lt;p&gt;To explain why this matters, Anand shared a story from a recent conversation with a CISO.&lt;/p&gt;
&lt;p&gt;The CISO described an agentic ransomware attack. The ransomware wasn't delivered by a human operator — it was propagated by an AI agent that handled every stage of the attack: target selection, social engineering, negotiation, and cryptocurrency collection. The victim communicated with what they believed was a human throughout. The pauses, the urgency, the negotiation cadence — all machine-generated. When the decryption key was provided and the ransom paid, the agent simply disappeared.&lt;/p&gt;
&lt;p&gt;The security implication is precise: the adversary is no longer a human operating a tool. It's a system. That system doesn't get tired, doesn't make social-engineering mistakes, doesn't operate under human time constraints, and can run multiple simultaneous attack campaigns. A defense built on detecting human behavioral patterns has a structural blind spot.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-roadmap-toward-self-improving-security&quot;&gt;The Roadmap: Toward Self-Improving Security&lt;/h3&gt;
&lt;p&gt;Anand described where F5 is taking this capability. The current version requires human approval before new AI-generated signatures go into production. The future state they're building toward is an agentic defense system that can operate continuously — analyzing traffic, generating candidate signatures, running them through controlled tests, and presenting validated signatures to humans for production deployment.&lt;/p&gt;
&lt;p&gt;The human stays in the loop for the final decision. Everything before that step runs automatically. The result is a security system that continuously improves itself, compounding accuracy and efficiency over time, without requiring a human to perform the analytical work that currently creates latency between discovery and protection.&lt;/p&gt;
&lt;p&gt;&quot;There will be a time when AI will be good enough,&quot; Anand said. &quot;We're not there yet. But we want to design for that world now.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-token-economy-and-big-ip-on-bluefield&quot;&gt;The Token Economy and BIG-IP on BlueField&lt;/h3&gt;
&lt;p&gt;The closing keynote covered more than the WAF. Anand spent significant time on what he called the token economy — the economics that govern AI inference at scale.&lt;/p&gt;
&lt;p&gt;He framed the metric shift clearly. For application and API teams, the unit of measure has always been requests per second. For AI workloads, the unit that matters is tokens per second. The five variables that govern performance in the token economy are throughput (tokens per second), perceived responsiveness (time to first token), cost (cost per token), total latency (end-to-end response time), and energy efficiency (tokens per watt).&lt;/p&gt;
&lt;p&gt;That last variable, he argued, is the most important constraint at scale. Energy is the ultimate thermodynamic limit on AI infrastructure. Organizations that aren't optimizing for tokens per watt are burning margins as well as power.&lt;/p&gt;
&lt;p&gt;F5's answer at the infrastructure layer is BIG-IP running on NVIDIA's BlueField DPU. The thesis: handle delivery and security before requests ever reach the GPU cluster, in a single pass, using the DPU's dedicated processing capacity.&lt;/p&gt;
&lt;p&gt;The independent performance numbers from third-party testing:&lt;/p&gt;
&lt;p&gt;40% increase in total throughput. For organizations running AI inference at scale, that's more tokens from the same hardware.&lt;/p&gt;
&lt;p&gt;60% reduction in time to first token. This is the metric that governs perceived intelligence — how quickly the model starts responding. A 60% improvement is the difference between a model that feels responsive and one that feels slow.&lt;/p&gt;
&lt;p&gt;30% improvement in end-to-end latency.&lt;/p&gt;
&lt;p&gt;F5 announced that iRules, expanded token counting, and AI Guardrails are all coming to the DPU platform over the next year. The goal is to bring the same delivery and security controls available at the application layer directly into the AI factory infrastructure — so that as AI inference scales, the governance capabilities scale with it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-this-means-for-engineering-teams&quot;&gt;What This Means for Engineering Teams&lt;/h3&gt;
&lt;p&gt;For teams running web applications, the practical implication of an AI-enhanced WAF is that the signature maintenance burden is reduced. Not eliminated — the static signature layer still runs — but the manual tuning work required to keep false positives manageable is significantly reduced when the AI layer is doing behavioral analysis in parallel.&lt;/p&gt;
&lt;p&gt;For teams building AI inference infrastructure, the BIG-IP on BlueField numbers present a compelling case for rethinking where security and delivery processing occur. If you can get 40% more throughput from existing hardware by moving those workloads to a DPU, the math on when to add more GPU capacity changes.&lt;/p&gt;
&lt;p&gt;The broader argument Anand made at the close of AppWorld is worth taking seriously. The path from a user prompt to a token — ingress, workflows, inference — has control points at every stage. The organizations that understand those control points and apply appropriate security and delivery controls at each one will be better positioned as AI workloads grow. Those that don't will find the problem harder to address later, when traffic volumes are higher and the attack surface is wider.&lt;/p&gt;
&lt;p&gt;The bottleneck, as Anand put it, is no longer source code. It's imagination. And the infrastructure that runs what imagination produces needs to be ready for it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12904/f5s-ai-powered-waf-stopped-a-chatgpt-generated-attack-without-a-single-signature</guid>
<pubDate>Sun, 15 Mar 2026 21:27:17 +0000</pubDate>
</item>
<item>
<title>NGINX Is Already in Front of Your AI Workloads. F5 Says That's the Point.</title>
<link>https://coderlegion.com/12827/nginx-is-already-in-front-of-your-ai-workloads-f5-says-thats-the-point</link>
<description>&lt;p&gt;When enterprises start deploying AI workloads, they typically go looking for a dedicated AI gateway. F5 thinks that's the wrong instinct — and they have a straightforward argument for why.&lt;/p&gt;
&lt;p&gt;About 50% of NGINX deployments already sit in front of Kubernetes clusters. When AI workloads land in those same clusters, NGINX is already there. It's already trusted. It's already in the traffic path. The question F5 is answering with its NGINX AI gateway strategy isn't &quot;how do we build something new?&quot; It's &quot;why would you add something when what you need is already running?&quot;&lt;/p&gt;
&lt;p&gt;Shawn Wormke, SVP of Product Management at F5, put it plainly during AppWorld 2026 in Las Vegas: &quot;It doesn't require people to change. And I think that's the biggest differentiator.&quot;&lt;/p&gt;
&lt;p&gt;That's the bet. And for developer and platform teams who have spent years standardizing on NGINX, it's worth understanding what that actually means in practice.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-mcp-traffic-is-revealing&quot;&gt;What MCP Traffic Is Revealing&lt;/h3&gt;
&lt;p&gt;One of the more significant things coming out of F5's NGINX work isn't a feature — it's what visibility into MCP traffic is showing enterprises about their own environments.&lt;/p&gt;
&lt;p&gt;MCP, the Model Context Protocol, is the emerging standard for how AI agents communicate with tools, APIs, and data sources. When NGINX parses MCP metadata, it gives operations teams a view into AI activity that most enterprises didn't know they were missing.&lt;/p&gt;
&lt;p&gt;What they're finding is that more AI is already running than anyone realized. More sensitive data is being accessed. More shadow AI is operating in the background. The visibility problem turns out to be bigger than the security problem — and you can't address the second until you've solved the first.&lt;/p&gt;
&lt;p&gt;For platform teams, this is a meaningful shift. The same proxy that handles your web traffic can now surface what your AI agents are actually doing, who they're talking to, and what data they're touching. No new tooling required. No rearchitecting the stack.&lt;/p&gt;
&lt;p&gt;NGINX MCP visibility is now generally available in NGINX Open Source. Enterprise support is available through NGINX Plus.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-ai-gateway-argument&quot;&gt;The AI Gateway Argument&lt;/h3&gt;
&lt;p&gt;The case for a separate AI gateway usually goes something like this: AI traffic is different, AI models need specialized routing, and purpose-built tools handle it better than general-purpose infrastructure.&lt;/p&gt;
&lt;p&gt;F5's counter-argument is operational. A dedicated AI gateway means another tool to deploy, another vendor to manage, another configuration surface to maintain, and another potential point of failure between your users and your models. If NGINX is already handling traffic management, TLS termination, and load balancing for the applications sitting next to your AI workloads, adding a separate gateway layer creates complexity without adding capability.&lt;/p&gt;
&lt;p&gt;The argument gets sharper when you look at where enterprises actually are in their AI deployments. Wormke was direct about this at AppWorld: the narrative about enterprise AI adoption doesn't always match reality. &quot;If you read everything on the internet about AI, you think all of these customers are doing all this stuff. But the reality is, some are. A lot of enterprises are just trying to figure it out.&quot;&lt;/p&gt;
&lt;p&gt;For the majority of organizations still in early deployment stages, the last thing they need is infrastructure sprawl. NGINX as an AI gateway works because it's already there.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-nginx-handles-in-the-ai-traffic-path&quot;&gt;What NGINX Handles in the AI Traffic Path&lt;/h3&gt;
&lt;p&gt;Within F5's ADSP platform, NGINX addresses several specific problems in the AI traffic path.&lt;/p&gt;
&lt;p&gt;Routing and load balancing for AI model endpoints works the same way it works for any application — with the addition of LLM-specific routing logic that can direct requests based on model type, token budget, or other AI-specific parameters.&lt;/p&gt;
&lt;p&gt;Rate limiting and quota enforcement matter more with AI workloads than with traditional applications because token costs are real and variable. NGINX can enforce limits at the gateway layer before requests ever reach the model.&lt;/p&gt;
&lt;p&gt;Observability via MCP parsing gives teams visibility into agent behavior — what actions agents are taking, what tools they're calling, and what data they're accessing. This is genuinely new capability. Traditional application observability doesn't capture the semantic content of AI interactions. MCP visibility does.&lt;/p&gt;
&lt;p&gt;Security controls apply the same WAF and bot defense capabilities that already protect your web applications to AI traffic — including protection against prompt injection and model abuse.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-rollout-sequence&quot;&gt;The Rollout Sequence&lt;/h3&gt;
&lt;p&gt;F5 is sequencing the NGINX AI gateway capabilities based on where they see the most immediate customer pain. Automated certificate management is first — operationally unglamorous, but a real friction point for teams managing certificates across large NGINX deployments. AI-specific capabilities build on top of that foundation.&lt;/p&gt;
&lt;p&gt;The broader NGINX roadmap runs through 2026 and into 2027 as part of F5's ADSP platform expansion. BIG-IP got F5 Insight first. NGINX and Distributed Cloud follow as the platform matures.&lt;/p&gt;
&lt;p&gt;For developer and platform teams already running NGINX, the practical implication is straightforward: the infrastructure you have is being extended to handle AI workloads without requiring you to replace it or work around it. That's a different kind of product story than most of what gets announced at conferences. It's not about adding something new. It's about making what you already trust more capable.&lt;/p&gt;
&lt;p&gt;And in a period when most engineering teams are already managing more complexity than they'd like, that distinction matters.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12827/nginx-is-already-in-front-of-your-ai-workloads-f5-says-thats-the-point</guid>
<pubDate>Fri, 13 Mar 2026 02:16:08 +0000</pubDate>
</item>
<item>
<title>How I Built a Secure Reverse Proxy with Nginx</title>
<link>https://coderlegion.com/12550/how-i-built-a-secure-reverse-proxy-with-nginx</link>
<description>&lt;p&gt;A reverse proxy is one of the most powerful building blocks in modern web infrastructure. It sits between users and your backend services, acting as a &lt;strong&gt;gatekeeper&lt;/strong&gt; that can improve performance, enforce security policies, and control traffic.&lt;/p&gt;
&lt;p&gt;In this article, I'll walk through how I built a &lt;strong&gt;secure reverse proxy using Nginx&lt;/strong&gt;, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reverse proxy architecture&lt;/li&gt;
&lt;li&gt;Rate limiting to stop abuse&lt;/li&gt;
&lt;li&gt;Security headers for safer responses&lt;/li&gt;
&lt;li&gt;Request logging for visibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end, you'll have a practical setup you can deploy in front of your applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;reverse-proxy-architecture&quot;&gt;Reverse Proxy Architecture&lt;/h2&gt;
&lt;p&gt;At a high level, a reverse proxy sits between clients and your backend servers.&lt;/p&gt;
&lt;p&gt;Instead of users directly accessing your application server, all requests first go through the proxy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Client
   │
   ▼
Reverse Proxy (Nginx)
   │
   ▼
Application Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this architecture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hide internal infrastructure&lt;/li&gt;
&lt;li&gt;Centralize security policies&lt;/li&gt;
&lt;li&gt;Terminate TLS in one place&lt;/li&gt;
&lt;li&gt;Add traffic filtering and monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example scenario:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Internet
   │
   ▼
Nginx Reverse Proxy
   ├── API Server (Node.js)
   ├── Web App (React / Next.js)
   └── Admin Panel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes Nginx the &lt;strong&gt;entry point for all incoming traffic&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;basic-nginx-reverse-proxy-configuration&quot;&gt;Basic Nginx Reverse Proxy Configuration&lt;/h2&gt;
&lt;p&gt;A simple reverse proxy configuration might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
    listen 80;

    server_name example.com;

    location / {
        proxy_pass http://backend_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accepts HTTP requests&lt;/li&gt;
&lt;li&gt;Forwards them to the backend application&lt;/li&gt;
&lt;li&gt;Preserves client IP information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the &lt;strong&gt;foundation&lt;/strong&gt;, but by itself it doesn't provide much security.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;adding-rate-limiting&quot;&gt;Adding Rate Limiting&lt;/h2&gt;
&lt;p&gt;Public services often face &lt;strong&gt;bot traffic, scanners, and brute-force attacks&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Rate limiting helps protect your application by restricting how many requests a client can make.&lt;/p&gt;
&lt;p&gt;First, define a rate limit zone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then apply it in your server block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;location /api/ {
    limit_req zone=api_limit burst=20 nodelay;
    proxy_pass http://backend_api;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Limit: 10 requests per second
Burst: allow short spikes
Protects API endpoints from abuse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rate limiting is especially effective against &lt;strong&gt;automated scanners and brute-force attacks&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;adding-security-headers&quot;&gt;Adding Security Headers&lt;/h2&gt;
&lt;p&gt;Security headers help protect browsers and users from common web vulnerabilities.&lt;/p&gt;
&lt;p&gt;You can add them directly in Nginx.&lt;/p&gt;
&lt;p&gt;Example configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;add_header X-Frame-Options &quot;SAMEORIGIN&quot;;
add_header X-Content-Type-Options &quot;nosniff&quot;;
add_header X-XSS-Protection &quot;1; mode=block&quot;;
add_header Referrer-Policy &quot;no-referrer-when-downgrade&quot;;
add_header Content-Security-Policy &quot;default-src 'self'&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These headers help defend against:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;clickjacking
content-type sniffing
cross-site scripting
data leakage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They add an additional &lt;strong&gt;layer of client-side protection&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;enabling-access-logging&quot;&gt;Enabling Access Logging&lt;/h2&gt;
&lt;p&gt;Logging is essential for &lt;strong&gt;visibility and incident response&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With Nginx logs you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;detect suspicious traffic&lt;/li&gt;
&lt;li&gt;identify attack patterns&lt;/li&gt;
&lt;li&gt;debug application issues&lt;/li&gt;
&lt;li&gt;analyze performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also customize log formats:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;log_format security_log '$remote_addr - $request - $status - $http_user_agent';

access_log /var/log/nginx/security.log security_log;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful data in logs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;client IP
request path
HTTP status codes
user agents
request timing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Logs often reveal &lt;strong&gt;automated scanning and attack attempts&lt;/strong&gt; hitting your endpoints.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;extra-hardening-tips&quot;&gt;Extra Hardening Tips&lt;/h2&gt;
&lt;p&gt;In production environments, you may want to go further.&lt;/p&gt;
&lt;p&gt;Additional best practices:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Enable HTTPS with TLS
Disable unnecessary HTTP methods
Hide server version information
Restrict admin endpoints
Use upstream health checks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server_tokens off;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prevents Nginx from exposing its version number in responses.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;A reverse proxy is much more than just a traffic router — it can act as a &lt;strong&gt;powerful security layer&lt;/strong&gt; in front of your applications.&lt;/p&gt;
&lt;p&gt;With a few Nginx configurations you can implement:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;traffic control
security headers
request logging
basic attack mitigation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These protections significantly reduce your exposure to automated attacks.&lt;/p&gt;
&lt;p&gt;If you want a ready-to-use security layer, projects like &lt;a rel=&quot;nofollow&quot; href=&quot;https://safepoint.cloud/landing/safeline&quot;&gt;&lt;strong&gt;SafeLine WAF&lt;/strong&gt;&lt;/a&gt; implement many of these protections &lt;strong&gt;out of the box&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12550/how-i-built-a-secure-reverse-proxy-with-nginx</guid>
<pubDate>Wed, 11 Mar 2026 06:55:31 +0000</pubDate>
</item>
<item>
<title>Inside a Web Application Firewall: How WAFs Actually Inspect HTTP Traffic</title>
<link>https://coderlegion.com/12416/inside-a-web-application-firewall-how-wafs-actually-inspect-http-traffic</link>
<description>&lt;blockquote&gt;&lt;p&gt;A practical look at how Web Application Firewalls analyze HTTP requests, detect attacks, and fit into modern DevOps infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;A &lt;strong&gt;Web Application Firewall (WAF)&lt;/strong&gt; protects web applications by inspecting &lt;strong&gt;HTTP requests at the application layer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unlike traditional firewalls, it analyzes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URLs&lt;/li&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Cookies&lt;/li&gt;
&lt;li&gt;Request bodies&lt;/li&gt;
&lt;li&gt;File uploads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WAFs detect attacks like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL Injection&lt;/li&gt;
&lt;li&gt;Cross-Site Scripting (XSS)&lt;/li&gt;
&lt;li&gt;Command Injection&lt;/li&gt;
&lt;li&gt;File Upload Exploits&lt;/li&gt;
&lt;li&gt;Application-layer DDoS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But they work best when combined with &lt;strong&gt;secure coding practices and proper DevOps observability&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-waf-still-matters-in-modern-infrastructure&quot;&gt;Why WAF Still Matters in Modern Infrastructure&lt;/h2&gt;
&lt;p&gt;If your API or web service is public, it is already being scanned.&lt;/p&gt;
&lt;p&gt;Typical traffic hitting public endpoints often looks like this:&lt;/p&gt;
&lt;p&gt;GET /phpmyadmin&lt;br&gt;
GET /.env&lt;br&gt;
GET /wp-login.php&lt;br&gt;
GET /admin&lt;br&gt;
GET /api/users?id=1 OR 1=1&lt;/p&gt;
&lt;p&gt;These requests are usually &lt;strong&gt;automated vulnerability scans&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A WAF acts as a &lt;strong&gt;filter between the internet and your application&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Typical deployment architecture:&lt;/p&gt;
&lt;p&gt;Internet&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
+-----------+&lt;br&gt;
|   CDN     |&lt;br&gt;
+-----------+&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
+-----------+&lt;br&gt;
|   WAF     |&lt;br&gt;
+-----------+&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
+-----------+&lt;br&gt;
| Load Bal. |&lt;br&gt;
+-----------+&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
+-----------+&lt;br&gt;
| Web App   |&lt;br&gt;
+-----------+&lt;/p&gt;
&lt;p&gt;The WAF intercepts every HTTP request before it reaches the backend.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-by-step-how-a-waf-processes-a-request&quot;&gt;Step-by-Step: How a WAF Processes a Request&lt;/h2&gt;
&lt;p&gt;A typical WAF request lifecycle looks like this:&lt;/p&gt;
&lt;p&gt;Client Request&lt;br&gt;
|&lt;br&gt;
v&lt;br&gt;
Request Parsing&lt;br&gt;
|&lt;br&gt;
v&lt;br&gt;
Rule Matching&lt;br&gt;
|&lt;br&gt;
v&lt;br&gt;
Risk Scoring&lt;br&gt;
|&lt;br&gt;
v&lt;br&gt;
Allow / Block / Log&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-1-http-request-parsing&quot;&gt;Step 1: HTTP Request Parsing&lt;/h2&gt;
&lt;p&gt;Before detection begins, the WAF extracts request fields.&lt;/p&gt;
&lt;p&gt;Example request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /api/login HTTP/1.1
Host: example.com
User-Agent: sqlmap
Content-Type: application/json

{
 &quot;username&quot;:&quot;admin&quot;,
 &quot;password&quot;:&quot;' OR 1=1 --&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The WAF parses this request into structured fields:&lt;/p&gt;
&lt;p&gt;requestMethod = POST&lt;br&gt;
requestURI = /api/login&lt;br&gt;
userAgent = sqlmap&lt;br&gt;
requestBody = JSON payload&lt;br&gt;
headers = HTTP headers&lt;/p&gt;
&lt;p&gt;These fields are then inspected by the detection engine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-rule-matching&quot;&gt;Step 2: Rule Matching&lt;/h2&gt;
&lt;p&gt;Most WAF engines rely on signature-based detection.&lt;/p&gt;
&lt;p&gt;Example rule detecting SQL injection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;rule_id: 10001
description: Detect SQL injection
match:
  field: requestBody
  pattern: &quot;(union select|or 1=1|sleep\()&quot;
action: block
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example rule detecting vulnerability scanners:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;rule_id: 20002
description: Block vulnerability scanners
match:
  field: userAgent
  pattern: &quot;(sqlmap|nikto|nmap)&quot;
action: block
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a request matches a rule, the configured action is executed.&lt;/p&gt;
&lt;h3 id=&quot;example-modsecurity-rule&quot;&gt;Example: ModSecurity Rule&lt;/h3&gt;
&lt;p&gt;Many production deployments use &lt;strong&gt;NGINX + ModSecurity&lt;/strong&gt; as a Web Application Firewall.&lt;br&gt;
Below is an example rule that detects common SQL injection patterns in incoming requests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;SecRule REQUEST_URI|ARGS|REQUEST_BODY &quot;@rx (union\s+select|sleep\(|benchmark\()&quot; \
&quot;id:10010,\
phase:2,\
block,\
msg:'SQL Injection attempt detected'&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Parameter      &lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;REQUEST_URI&lt;/code&gt;  &lt;/td&gt;&lt;td&gt;Inspect the URL path&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ARGS&lt;/code&gt;         &lt;/td&gt;&lt;td&gt;Inspect query parameters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;REQUEST_BODY&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Inspect POST request body&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@rx&lt;/code&gt;          &lt;/td&gt;&lt;td&gt;Perform regex pattern matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;phase:2&lt;/code&gt;      &lt;/td&gt;&lt;td&gt;Execute rule after request body parsing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;block&lt;/code&gt;        &lt;/td&gt;&lt;td&gt;Deny the request&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-3-request-scoring&quot;&gt;Step 3: Request Scoring&lt;/h2&gt;
&lt;p&gt;Modern WAFs rarely block based on a single rule.&lt;/p&gt;
&lt;p&gt;Instead, they use a risk scoring model.&lt;/p&gt;
&lt;p&gt;Example scoring system:&lt;/p&gt;
&lt;p&gt;SQL injection pattern      +5&lt;br&gt;
XSS payload                +4&lt;br&gt;
Scanner user-agent         +2&lt;br&gt;
Suspicious encoding        +2&lt;/p&gt;
&lt;p&gt;Example threshold:&lt;/p&gt;
&lt;p&gt;score &amp;gt;= 5 → block request&lt;br&gt;
score &amp;lt; 5  → allow request&lt;/p&gt;
&lt;p&gt;This approach reduces false positives.&lt;/p&gt;
&lt;h2 id=&quot;real-attack-example&quot;&gt;Real Attack Example&lt;/h2&gt;
&lt;p&gt;Example malicious request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /api/products?id=1 UNION SELECT username,password FROM users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Detected pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;union select
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Response:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP 403 Forbidden
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request never reaches the backend server.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;devops-example-rate-limiting&quot;&gt;DevOps Example: Rate Limiting&lt;/h2&gt;
&lt;p&gt;Application-layer attacks often involve high request rates.&lt;/p&gt;
&lt;p&gt;Example NGINX rate limiting configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;limit_req_zone $binary_remote_addr zone=login_limit:10m rate=10r/s;

server {

  location /login {

    limit_req zone=login_limit burst=20 nodelay;

  }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This protects login endpoints from:&lt;/p&gt;
&lt;p&gt;brute force attacks&lt;/p&gt;
&lt;p&gt;credential stuffing&lt;/p&gt;
&lt;p&gt;CC attacks&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;waf-logging-for-security-monitoring&quot;&gt;WAF Logging for Security Monitoring&lt;/h2&gt;
&lt;p&gt;Logging is critical for security operations.&lt;/p&gt;
&lt;p&gt;Example log entry:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
 &quot;timestamp&quot;:&quot;2026-03-05T11:22:09Z&quot;,
 &quot;client_ip&quot;:&quot;192.168.1.15&quot;,
 &quot;uri&quot;:&quot;/login&quot;,
 &quot;rule_id&quot;:10010,
 &quot;attack_type&quot;:&quot;SQL Injection&quot;,
 &quot;action&quot;:&quot;blocked&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These logs can be forwarded to:&lt;/p&gt;
&lt;p&gt;SIEM systems&lt;/p&gt;
&lt;p&gt;security monitoring platforms&lt;/p&gt;
&lt;p&gt;incident response pipelines&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-biggest-challenge-false-positives&quot;&gt;The Biggest Challenge: False Positives&lt;/h2&gt;
&lt;p&gt;No WAF is perfect.&lt;/p&gt;
&lt;p&gt;Example legitimate request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /search?q=select+product+from+store
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A poorly designed rule might detect select as SQL injection.&lt;/p&gt;
&lt;p&gt;Solutions include:&lt;/p&gt;
&lt;p&gt;whitelisting trusted endpoints&lt;/p&gt;
&lt;p&gt;adjusting rule thresholds&lt;/p&gt;
&lt;p&gt;disabling noisy signatures&lt;/p&gt;
&lt;p&gt;Proper tuning is essential for production environments.&lt;/p&gt;
&lt;h3 id=&quot;practical-advice-for-engineers&quot;&gt;Practical Advice for Engineers&lt;/h3&gt;
&lt;p&gt;If you operate production systems, consider the following best practices.&lt;/p&gt;
&lt;h4 id=&quot;1-always-deploy-a-waf&quot;&gt;1. Always deploy a WAF&lt;/h4&gt;
&lt;p&gt;Especially for services that are directly exposed to the internet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Public APIs  &lt;/li&gt;
&lt;li&gt;Authentication endpoints (e.g., &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/auth&lt;/code&gt;)  &lt;/li&gt;
&lt;li&gt;File upload services  &lt;/li&gt;
&lt;li&gt;Payment or account management systems  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A WAF can filter large amounts of automated attack traffic before it reaches your application layer.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-start-in-monitor-mode&quot;&gt;2. Start in &lt;strong&gt;monitor mode&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Before enabling blocking rules, it is recommended to run the WAF in &lt;strong&gt;monitor (or detection-only) mode&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This allows engineers to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;observe real traffic patterns  &lt;/li&gt;
&lt;li&gt;identify potential false positives  &lt;/li&gt;
&lt;li&gt;understand how rules interact with the application  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only after analyzing logs and tuning rules should blocking be fully enabled.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-continuously-tune-rule-sets&quot;&gt;3. Continuously tune rule sets&lt;/h4&gt;
&lt;p&gt;No rule set works perfectly out of the box.&lt;/p&gt;
&lt;p&gt;Production environments often require continuous adjustments such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;disabling overly aggressive rules  &lt;/li&gt;
&lt;li&gt;adjusting anomaly score thresholds  &lt;/li&gt;
&lt;li&gt;creating endpoint-specific rule exceptions  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regular rule tuning significantly reduces false positives while maintaining protection.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-implement-layered-security&quot;&gt;4. Implement layered security&lt;/h4&gt;
&lt;p&gt;A WAF should not be treated as the only security control.&lt;/p&gt;
&lt;p&gt;Instead, it should be part of a &lt;strong&gt;defense-in-depth strategy&lt;/strong&gt; that includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;input validation at the application layer  &lt;/li&gt;
&lt;li&gt;strong authentication mechanisms  &lt;/li&gt;
&lt;li&gt;rate limiting and request throttling  &lt;/li&gt;
&lt;li&gt;vulnerability scanning and patch management  &lt;/li&gt;
&lt;li&gt;centralized logging and monitoring  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining multiple defensive layers greatly improves overall security posture.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;A Web Application Firewall is essentially a &lt;strong&gt;programmable HTTP inspection engine&lt;/strong&gt; that sits between the internet and your application.&lt;/p&gt;
&lt;p&gt;By analyzing requests in real time, it can detect and block a wide range of common web attacks before they reach backend services.&lt;/p&gt;
&lt;p&gt;However, the effectiveness of a WAF depends heavily on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proper configuration  &lt;/li&gt;
&lt;li&gt;continuous rule tuning  &lt;/li&gt;
&lt;li&gt;integration with monitoring and security operations  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When deployed correctly, a WAF can dramatically reduce the attack surface of modern web applications while providing valuable visibility into malicious traffic patterns.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12416/inside-a-web-application-firewall-how-wafs-actually-inspect-http-traffic</guid>
<pubDate>Fri, 06 Mar 2026 11:36:34 +0000</pubDate>
</item>
<item>
<title>Nginx Setup in 6 Steps: From Installation to Production </title>
<link>https://coderlegion.com/10823/nginx-setup-in-6-steps-from-installation-to-production</link>
<description>&lt;p&gt;Solved every nginx problem I encountered while building production servers. Here's the complete guide!&lt;/p&gt;
&lt;h3 id=&quot;problems-solutions&quot;&gt;Problems → Solutions&lt;/h3&gt;
&lt;h4 id=&quot;-where-do-i-even-start-&quot;&gt;&quot;Where do I even start?&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#vps-server&quot;&gt;VPS Selection Guide&lt;/a&gt;&lt;/strong&gt; - nginx minimum requirements, sizing guide, hosting recommendations&lt;/p&gt;
&lt;h4 id=&quot;-nginx-won-t-start-after-installation-&quot;&gt;&quot;nginx won't start after installation&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#nginx-configuration&quot;&gt;Proper Installation&lt;/a&gt;&lt;/strong&gt; - nginx.conf with correct syntax, test with &lt;code&gt;nginx -t&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;-ssl-errors-https-not-working-&quot;&gt;&quot;SSL errors / HTTPS not working&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#nginx-domain&quot;&gt;Domain Configuration&lt;/a&gt;&lt;/strong&gt; - Complete nginx SSL/TLS setup, HTTP/2 configuration, certificate paths&lt;/p&gt;
&lt;h4 id=&quot;-413-request-entity-too-large-&quot;&gt;&quot;413 Request Entity Too Large&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#file-upload&quot;&gt;Upload Fix&lt;/a&gt;&lt;/strong&gt; - Configure client_max_body_size properly&lt;/p&gt;
&lt;h4 id=&quot;-slow-performance-high-cpu-usage-&quot;&gt;&quot;Slow performance / high CPU usage&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#htaccess-file&quot;&gt;Performance Optimization&lt;/a&gt;&lt;/strong&gt; - Enable gzip, caching, optimize buffers&lt;/p&gt;
&lt;h4 id=&quot;-poor-search-engine-visibility-&quot;&gt;&quot;Poor search engine visibility&quot;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#robots-txt&quot;&gt;SEO Setup&lt;/a&gt;&lt;/strong&gt; - &lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#robots-txt&quot;&gt;robots.txt&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#sitemap-xml&quot;&gt;sitemap.xml&lt;/a&gt; configuration&lt;/p&gt;
&lt;h3 id=&quot;additional-help&quot;&gt;Additional Help&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx#faq&quot;&gt;Comprehensive FAQ&lt;/a&gt; section&lt;/li&gt;
&lt;li&gt;nginx best practices for avoiding issues&lt;/li&gt;
&lt;li&gt;Copy-paste configs that actually work&lt;/li&gt;
&lt;li&gt;nginx performance tuning tips&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kolodych.com/articles/nginx&quot;&gt;Troubleshoot now →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No more nginx frustration! ✨&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10823/nginx-setup-in-6-steps-from-installation-to-production</guid>
<pubDate>Sat, 31 Jan 2026 12:01:13 +0000</pubDate>
</item>
<item>
<title>Deploy a Next.js App on AWS EC2 with Docker, NGINX, and Automate with GitHub Actions.</title>
<link>https://coderlegion.com/5757/deploy-a-next-js-app-on-aws-ec2-with-docker-nginx-and-automate-with-github-actions</link>
<description>&lt;p&gt;Next.js is a powerful React framework that provides server-side rendering, static site generation, and API routes. Deploying a Next.js app on an AWS EC2 instance allows for better control and customization of the hosting environment. This guide will walk you through the process of setting up a Next.js application on AWS EC2 using Docker and NGINX while automating deployment with GitHub Actions.&lt;/p&gt;
&lt;p&gt;Before following this Next.js tutorial, we recommend that you first consult Build and Deploy a ReactJS App to AWS EC2 with Docker, NGINX, and Automate with GitHub Actions. This guide is a continuation of the series on React deployment, focusing solely on Next.js-specific updates. The basic steps remain the same, and you can refer to the previous article for more details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Setting Up the &lt;strong&gt;Project&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dockerizing&lt;/strong&gt; the NextJS application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up &lt;strong&gt;NGINX&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automating deployment with GitHub Actions on &lt;strong&gt;AWS EC2&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Note that you can find the link of final source code in the&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;http://%20https://github.com/kilamaelie/Deploy-a-Next.js-App-on-AWS-EC2-with-Docker-NGINX-and-Automate-with-GitHub-Actions&quot;&gt;https://github.com/kilamaelie/Deploy-a-Next.js-App-on-AWS-EC2-with-Docker-NGINX-and-Automate-with-GitHub-Actions&lt;/a&gt;&lt;br&gt;
repository.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1. Setting Up the Project&lt;/strong&gt;&lt;br&gt;
Open your terminal and navigate to the directory where you want to create the project, for the purpose of this tutorial we’ll create a directory with a name &lt;code&gt;next-tutorial-app&lt;/code&gt; or you can name it anything depending on your project name. This folder is considered as the &lt;code&gt;root&lt;/code&gt; directory for our application which contains all folders and files structures.&lt;/p&gt;
&lt;p&gt;Then, we will change our directory into the &lt;code&gt;next-tutorial-app&lt;/code&gt; folder and all other parts of configuration files or folders will be in this folder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; mkdir next-tutorial-app
&amp;gt; cd next-tutorial-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;next-tutorial-app&lt;/code&gt; folder, to create a new Next project runs the following command using create-next-app@latest&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx create-next-app@latest # website is our app-name or folder name 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s change the directory into website as our app-name for NextJs application and start development server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd website # app-name
npm run dev # start the development server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Dockerizing the Next.js App&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in the root directory of your website folder:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:23.10.0-alpine

WORKDIR /app

COPY ./package*.json ./

# Install dependencies
RUN yarn

# Copy all files
COPY . .

# Expose the listening port
EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, create a &lt;code&gt;.dockerignore&lt;/code&gt; file to exclude unnecessary files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node_modules
.next
.DS_Store
dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your project’s &lt;code&gt;root&lt;/code&gt; directory as &lt;code&gt;next-tutorial-app&lt;/code&gt;, create a file named &lt;code&gt;docker-compose.yml&lt;/code&gt;. This file will contain the configuration for your multi-container application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  website:
    build:
      context: ./website
      dockerfile: Dockerfile
    command: yarn dev
    ports:
      - &quot;3000:3000&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So to test it, just go in your terminal, navigate to the directory containing the &lt;code&gt;docker-compose.yml&lt;/code&gt; file and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kill the task in the terminal where docker-compose up is running in order to stop the. To remove the containers and their associated resources, run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose rm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Setting Up NGINX as a Reverse Proxy in Docker&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the root of your project directory, create a Nginx directory named &lt;code&gt;nginx&lt;/code&gt; then create a configuration file named &lt;code&gt;nginx.conf&lt;/code&gt; . So inside nginx.conf file adds the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
     listen 80;
     server_name localhost; 
     #server_name can add your domain or Ip address for your server in the production
 
    location / {
        proxy_pass http://website:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in the same directory as your Nginx, create a &lt;code&gt;Dockerfile&lt;/code&gt; which defines how to build a custom Nginx Docker Image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Use an official Nginx image as the base image
FROM nginx:latest

# Remove any existing config files
RUN rm /etc/nginx/conf.d/*
# Copy the custom Nginx configuration
COPY nginx.conf /etc/nginx/conf.d/
# Expose port 80 for Nginx
EXPOSE 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before testing in development, let’s update our &lt;code&gt;docker-compose&lt;/code&gt; file by adding the Nginx service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  website:
    build:
      context: ./website
      dockerfile: Dockerfile
    ports:
    - 3000:3000
    
  nginx:
    build:
     context: ./nginx
     dockerfile: Dockerfile
    ports:
      - &quot;80:80&quot;
    depends_on:
      - website
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also update the &lt;code&gt;Dockerfile&lt;/code&gt; for the next app in the website directory&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Stage 1: Install Dependencies
ARG NODE=node:20-alpine 
FROM ${NODE} AS deps

RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY package*.json ./
RUN npm install

# Stage 2: Build
FROM ${NODE} AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Stage 3: Production Runner
FROM ${NODE} AS runner

WORKDIR /app

ENV NODE_ENV production
ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1

# Install PM2 globally
RUN npm install -g pm2

# Create a non-root user
RUN addgroup --system --gid 1001 nodegroup
RUN adduser --system --uid 1001 appuser

# Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder --chown=appuser:nodegroup /app/.next/standalone ./
COPY --from=builder --chown=appuser:nodegroup /app/.next/static ./.next/static

USER appuser

EXPOSE 3000

# Start the application using PM2
CMD [&quot;pm2-runtime&quot;, &quot;node&quot;, &quot;--&quot;, &quot;server.js&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before testing, ensure that output: '&lt;code&gt;standalone'&lt;/code&gt; is set in the &lt;code&gt;next.config.js&lt;/code&gt; file to optimize the build for Docker deployment.&lt;/p&gt;
&lt;p&gt;To test if everything works, open a terminal, navigate to your root directory, and run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up  #
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then let’s testing at &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:80&quot;&gt;http://localhost:80&lt;/a&gt; or &lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:80&quot;&gt;http://127.0.0.1:80&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kill the task in the terminal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose rm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, let’s create &lt;code&gt;docker-compose.ci&lt;/code&gt; and &lt;code&gt;docker-compose.prod&lt;/code&gt; files for the production and workflow purpose in the root directory of the project&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Docker-compose.ci

services:
  website:
    container_name: website
    build:
      context: ./website
      dockerfile: Dockerfile
      cache_from:
        - '${NEXT_APP_WEBSITE_IMAGE}'
    image: '${NEXT_APP_WEBSITE_IMAGE}'
    env_file:
      - ./.env
    ports:
      - '3000:3000'

  nginx:
    container_name: nginx
    build:
      context: ./nginx
      dockerfile: Dockerfile
      cache_from:
        - '${NEXT_APP_NGINX_IMAGE}'
    image: '${NEXT_APP_NGINX_IMAGE}'
    ports:
      - '80:80'
    restart: always
    depends_on:
      - website
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Docker-compose.prod

services:
  website:
    container_name: website
    image: '${NEXT_APP_WEBSITE_IMAGE}'
    env_file: .env
    ports:
      - '3000:3000'

  nginx:
    container_name: nginx
    image: '${NEXT_APP_NGINX_IMAGE}'
    ports:
      - '80:80'
    depends_on:
      - website
    restart: always
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So for this tutorial will use Github packages for hosting the images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Automating Deployment with GitHub Actions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a GitHub Actions workflow file at &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt; in the root of the project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Continuous Integration and Delivery

on: 
  push:
    branches: [main]
env:
  NEXT_APP_WEBSITE_IMAGE: ghcr.io/$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')/website
  NEXT_APP_NGINX_IMAGE: ghcr.io/$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')/nginx
  REGISTRY: ghcr.io
jobs:
  build:
    name: Build Docker Images
    runs-on: ubuntu-latest
    steps:
      - name: Checkout main
        uses: actions/checkout@v3

      - name: Set environment variables
        run: |
          echo &quot;NEXT_APP_WEBSITE_IMAGE=$(echo ${{env.NEXT_APP_WEBSITE_IMAGE}} )&quot; &amp;gt;&amp;gt; $GITHUB_ENV
          echo &quot;NEXT_APP_NGINX_IMAGE=$(echo ${{env.NEXT_APP_NGINX_IMAGE}} )&quot; &amp;gt;&amp;gt; $GITHUB_ENV
      - name: Log in to GitHub Packages
        env:
          PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
        run: echo ${PERSONAL_ACCESS_TOKEN} | docker login ghcr.io -u ${{ secrets.NAMESPACE }} --password-stdin

      - name: Build images
        run: |
          docker compose -f docker-compose.ci.yml build
      - name: Push images
        run: |
          docker push ${{ env.NEXT_APP_WEBSITE_IMAGE }}
          docker push ${{ env.NEXT_APP_NGINX_IMAGE }}
  deploy:
    name: Deploy to AWS EC2
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout main
        uses: actions/checkout@v3
      - name: Add environment variables to .env
        run: |
          echo NEXT_APP_WEBSITE_IMAGE=${{ env.NEXT_APP_WEBSITE_IMAGE }} &amp;gt;&amp;gt; .env
          echo NEXT_APP_NGINX_IMAGE=${{ env.NEXT_APP_NGINX_IMAGE }} &amp;gt;&amp;gt; .env
          echo NAMESPACE=${{ secrets.NAMESPACE }} &amp;gt;&amp;gt; .env
          echo PERSONAL_ACCESS_TOKEN=${{ secrets.PERSONAL_ACCESS_TOKEN }} &amp;gt;&amp;gt; .env
      - name: Add the private SSH key to the ssh-agent
        env:
          SSH_AUTH_SOCK: /tmp/ssh_agent.sock
        run: |
          mkdir -p ~/.ssh
          ssh-agent -a $SSH_AUTH_SOCK &amp;gt; /dev/null
          ssh-keyscan github.com &amp;gt;&amp;gt; ~/.ssh/known_hosts
          ssh-add - &amp;lt;&amp;lt;&amp;lt; &quot;${{ secrets.PRIVATE_KEY }}&quot;
      - name: Deploy images on AWS EC2
      
        env:
          SSH_AUTH_SOCK: /tmp/ssh_agent.sock

        run: |
          scp -o StrictHostKeyChecking=no -r ./.env ./docker-compose.prod.yml ${{secrets.AWS_HOST_USER}}@${{ secrets.AWS_EC2_IP_ADDRESS }}:
          ssh -o StrictHostKeyChecking=no ${{secrets.AWS_HOST_USER}}@${{ secrets.AWS_EC2_IP_ADDRESS }} &amp;lt;&amp;lt; EOF
              docker-compose down --rmi all -v
              docker login ghcr.io -u ${{secrets.NAMESPACE}} -p ${{secrets.PERSONAL_ACCESS_TOKEN}}
              docker pull $WEBSITE_IMAGE
              docker pull $NGINX_IMAGE 
              docker-compose --env-file=.env -f docker-compose.prod.yml up -d
              docker logout
          EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please make sure your on &lt;code&gt;main branch&lt;/code&gt; because the workflow’s can only trigger on the &lt;code&gt;main&lt;/code&gt; branch other you can it to your preferences.&lt;/p&gt;
&lt;p&gt;Workflow successfully finished :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4617544689212946738&quot; alt=&quot;workflow&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1806442423346209046&quot; alt=&quot;website&quot;&gt;&lt;/p&gt;
&lt;p&gt;  By following these steps, you have successfully deployed a Next.js application on AWS EC2 using Docker and NGINX, with automated deployments using GitHub Actions. You can further enhance this setup by integrating SSL certificates with Let’s Encrypt and using AWS services like RDS or S3 for storage.&lt;/p&gt;
&lt;p&gt;If you would like to improve your skills, click on the following link on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.patreon.com/kilamaelie/shop/master-react-app-deployment-aws-ec2-53-1089307?utm_medium=clipboard_copy&amp;amp;utm_source=copyLink&amp;amp;utm_campaign=productshare_creator&amp;amp;utm_content=join_link&quot;&gt;Master React App Deployment: AWS EC2, Route 53, SSL &amp;amp; GitHub Actions,A comprehensive manual which walks you through the deployment process, leveraging the power of Docker, AWS EC2, Route 53, SSL encryption and GitHub actions to build a robust CI/CD pipeline&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep exploring, stay curious and keep building on this foundation to create even more sophisticated and powerful projects. Happy coding!&lt;/p&gt;
&lt;p&gt;Thanks for reading through and I hope you liked what you read here. Feel free to connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://kilamaelie.dev&quot;&gt;portfolio&lt;/a&gt;,&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/kilamaelie/&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/kilamaelie&quot;&gt;twitter&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kilamaelie&quot;&gt;GitHub.&lt;/a&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/5757/deploy-a-next-js-app-on-aws-ec2-with-docker-nginx-and-automate-with-github-actions</guid>
<pubDate>Mon, 29 Sep 2025 12:54:42 +0000</pubDate>
</item>
<item>
<title>Deploy a ReactJS App to AWS EC2 with Docker, NGINX, and Automate with GitHub Actions part One</title>
<link>https://coderlegion.com/3478/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-one</link>
<description>&lt;p&gt;In today’s digital landscape, master the art of building, deploying and automating ReactJS Apps using Docker, NGINX and GitHub actions on AWS EC2. Dive into this comprehensive guide and transform your coding journey into an adventure of innovation, efficiency and cutting-edge technologies.&lt;/p&gt;
&lt;p&gt;Table of Contents&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setting Up the Project&lt;/li&gt;
&lt;li&gt;Dockerizing the ReactJS application&lt;/li&gt;
&lt;li&gt;Setting up NGINX&lt;/li&gt;
&lt;li&gt;Automating deployment with GitHub Actions on AWS EC2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1. Prerequisites&lt;/strong&gt;&lt;br&gt;
Before diving into this project, which involves several technologies and concepts, you need a solid grounding in the following areas to ensure successful learning and implementation such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Basic Web development: A good understanding of HTML, CSS and&lt;br&gt;
JavaScript is essential, as ReactJS is a JavaScript library for&lt;br&gt;
building user interfaces.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git and version control: A good knowledge of Git and version control&lt;br&gt;
concepts will help you manage your code base and collaborate&lt;br&gt;
effectively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker: Understanding containerization for docker is crucial to&lt;br&gt;
packaging your application and its dependencies in isolated&lt;br&gt;
containers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NGINX: A basic knowledge of NGINX as a web server and reverse proxy&lt;br&gt;
will be necessary to set up the server environment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember, while these requirements are essentials, you don’t need to be an expert in every area. You can learn and develop your expertise as you go along. What’s more, official documentation, online tutorials and communities can provide invaluable support as you learn.&lt;/p&gt;
&lt;p&gt;Note that you can find the link of final source code in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9034001942458065209&quot;&gt;build and deploy-reactApp to-aws ec2 via github-actions&lt;/a&gt; repository.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Setting Up the Project&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This involves several steps which including the installation, project structure and other basic configuration, here’s a step-by-step guide helping you get started :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install Node.js and npm:&lt;br&gt;
Before you begin, make sure Node.js and npm ( Node Package Managers) are installed on your system. You can download them from the official website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3479/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-two&quot;&gt;https://nodejs.org&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Create a New Project:&lt;br&gt;
Open your terminal and navigate to the directory where you want to create the project, for the purpose of this tutorial we’ll create a directory with a name &lt;code&gt;react-tutorial-app&lt;/code&gt; or you can name it anything depending on your project name. This folder is considered as the root directory for our application which contains all folders and files structures.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;In software development, “project structure” provides a clear and&lt;br&gt;
logical layout that helps developers understand and navigate the&lt;br&gt;
codebase more easily. A well-designed project structure promotes&lt;br&gt;
maintainability, collaboration, and efficient development.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, we will change our directory into the react-tutorial-app folder and all other parts of configuration files or folders will be in this folder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir react-tutorial-app
cd react-tutorial-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the react-tutorial-app folder, to create a new React project runs the following command using create-react-app and for more details on how create-react-app works and other staff, please visit the official website: [https://create-react-app.dev/][3]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx create-react-app your-app-name # website is our app-name or folder name 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;create-react-app&lt;/code&gt; is a command-line tool that allows you to quickly and easily set up a new &lt;code&gt;Reactjs&lt;/code&gt; Application, this boilerplate setup generates a basic project structure, including essential files and configuration needed to start building a React application.&lt;/p&gt;
&lt;p&gt;Let’s change the directory into website as our &lt;code&gt;app-name&lt;/code&gt; for React application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd website # app-name 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To start the development server and preview your React app, use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;This command will initiate the development server, which will compile your React code, bundle assets, and start a local web server to serve&lt;br&gt;
your application. It will also open your app in your default web&lt;br&gt;
browser automatically.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once the development server is up and running, you can access your React app by visiting &lt;code&gt;http://localhost:3000&lt;/code&gt; in your web browser. So well done ???? for creating a React application and setting up the project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Dockerizing the ReactJS application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Docker provides a standardized way to package and distribute applications, making it easier to deploy and manage across different environments. In this section, we’ll containerize our ReactJS application using Docker.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;If you haven’t already, install Docker on your machine by following the instructions for your operating system:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/get-docker/&quot;&gt;https://docs.docker.com/get-docker/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In your React app’s root directory, create a file named Dockerfile. This file defines the instructions to build your Docker image.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Use an official Node.js runtime as the base image

FROM node:19.6.0-alpine
# Set the working directory within the container
WORKDIR /website

# Copy package.json and package-lock.json to the container
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code to the container
COPY . .

# Build the React app
EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since in this project will have more than one container , then we ‘ll need to use &lt;code&gt;docker-compose&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker-compose&lt;/strong&gt; : allows you to define and run multiple Docker containers as a single service, making it easier to manage complex applications.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;If you haven’t already, you’ll need to install Docker Compose. Follow the installation instructions for your operating system:&lt;br&gt;
[https://docs.docker.com/compose/install/][4]&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In your project’s root directory as &lt;code&gt;react-tutorial-app&lt;/code&gt;, create a file named &lt;code&gt;docker-compose.yml&lt;/code&gt;. This file will contain the configuration for your multi-container application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  website:
    build:
      context: ./website
      dockerfile: Dockerfile
    command: npm start
    ports:
      - &quot;3000:3000&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines only one service website but a bit later more will be added. So in order to test it, just go in your terminal, navigate to the directory containing the &lt;code&gt;docker-compose.yml&lt;/code&gt; file and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker Compose will build the required images and start the defined containers. You’ll see logs from both services in the terminal and also depending on your configuration, you can access your ReactJS app at &lt;code&gt;http://localhost:3000&lt;/code&gt; . Kill the task in the terminal where &lt;code&gt;docker-compose up&lt;/code&gt; is running in order to stop the. To remove the containers and their associated resources, run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose rm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. Setting up NGINX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NGINX is a reverse proxy to serve the built static files of your React application. This setup is commonly used to improve performance and manage routing in production environments.&lt;/p&gt;
&lt;p&gt;In the root of your project directory, create a Nginx directory named &lt;code&gt;nginx&lt;/code&gt; then create a configuration file named nginx.conf . So inside &lt;code&gt;nginx.conf&lt;/code&gt; file adds the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
     listen 80;
     server_name localhost; 
     #server_name can add your domain or Ip address for your server in the production
 
     location / {
         root /usr/share/nginx/html/website;
         try_files $uri $uri/ /index.html;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in the same directory as your Nginx, create a &lt;code&gt;Dockerfile&lt;/code&gt; which defines how to build a custom Nginx Docker Image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Use an official Nginx image as the base image
FROM nginx:latest

# Remove any existing config files
RUN rm /etc/nginx/conf.d/*

# Copy the custom Nginx configuration
COPY nginx.conf /etc/nginx/conf.d/

# Expose port 80 for Nginx
EXPOSE 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before testing in development, let’s update our docker-compose file by adding the Nginx service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
website:
  build:
    context: ./website
    dockerfile: Dockerfile
  volumes:
   - website:/website/build/  # build files will be added at this directory !!!

nginx:
  build:
   context: ./nginx
   dockerfile: Dockerfile
  ports:
    - &quot;80:80&quot;
  volumes:
     - website:/usr/share/nginx/html/website # Copy React App's build files to the Nginx directory

volumes:
   website:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And also update the &lt;code&gt;Dockerfile&lt;/code&gt; for the react app in the website directory&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Use an official Node.js runtime as the base image

FROM node:19.6.0-alpine
# Set the working directory within the container
WORKDIR /website

# Copy package.json and package-lock.json to the container
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code to the container
COPY . .

# Build the React app
RUN npm run build

# Build the React app
EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the purpose of testing if everything works, we’ll open a terminal and navigate to your &lt;code&gt;root&lt;/code&gt; directory. Run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up  # 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then let’s testing at &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:80&quot;&gt;http://localhost:80&lt;/a&gt; or &lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:80&quot;&gt;http://127.0.0.1:80&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kill the task in the terminal where the container is running. To remove the container and its associated resources, run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose rm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, let’s create &lt;code&gt;docker-compose.ci&lt;/code&gt; and &lt;code&gt;docker-compose.prod&lt;/code&gt; files for the production and workflow purpose in the root directory of the project&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Docker-compose.ci&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  website:
    build:
      context: ./website
      dockerfile: Dockerfile
    cache_from:
        - '${WEBSITE_IMAGE}'
    image: '${WEBSITE_IMAGE}'
    ports:
      - &quot;3000:3000&quot;
    volumes:
      - website:./website/build/  # build files will be added at this directory !!!

  nginx:
    build:
     context: ./nginx
     dockerfile: Dockerfile
    cache_from:
        - '${NGINX_IMAGE}'
    image: '${NGINX_IMAGE}'
    ports:
      - &quot;80:80&quot;
    volumes:
      - website:/usr/share/nginx/html/website # Copy React App's build files to the Nginx directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Docker-compose.prod&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  website:
    container_name: 'website'
    image: '${WEBSITE_IMAGE}'
    ports:
      - &quot;3000:3000&quot;
    volumes:
      - website:./website/build/  # build files will be added at this directory !!!
  
  nginx:
    container_name: 'nginx'
    image: '${NGINX_IMAGE}'
    ports:
      - &quot;80:80&quot;
    volumes:
      - website:/usr/share/nginx/html/website # Copy React App's build files to the Nginx directory

volumes:
  website:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So for this tutorial will use Github packages for hosting the images.&lt;/p&gt;
&lt;p&gt;[GitHub Package Registry][5] is a package hosting service provided by GitHub. It allows you to publish, share, and manage software packages directly within your GitHub repositories.&lt;/p&gt;
&lt;p&gt;It lets you host your software, either publicly or privately, for use in your projects on GitHub.&lt;/p&gt;
&lt;p&gt;Click on the link for the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3479/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-two&quot;&gt;part two&lt;/a&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/3478/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-one</guid>
<pubDate>Wed, 25 Jun 2025 17:01:32 +0000</pubDate>
</item>
<item>
<title>Deploy a ReactJS App to AWS EC2 with Docker, NGINX, and Automate with GitHub Actions part two</title>
<link>https://coderlegion.com/3479/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-two</link>
<description>&lt;p&gt;&lt;strong&gt;AWS Setup&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s start by setting up an EC2 instance to deploy our application. To do this, and you’ll need to open an &lt;a rel=&quot;nofollow&quot; href=&quot;https://aws.amazon.com/&quot;&gt;AWS&lt;/a&gt; account (if you don’t already have one).&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If you don’t know how to set up AWS EC2 , please visit this link which&lt;br&gt;
will guide you through how to : &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/get-set-up-for-amazon-ec2.html&quot;&gt;launch EC2&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once we’ve finished configuring AWS EC2 and the instance is up and running, we can install Docker on it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;So connect via SSH into the instance using your Key Pair as :

$ ssh -i your-key-pair.pem ec2-user@&amp;lt;PUBLIC-IP-ADDRESS&amp;gt;

#example 
# ssh -i ~./ssh/react-tutorial.perm ec2-user@54.201.189.94
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After accessing the instance, start by updating the server package and installing the latest version of Docker and &lt;code&gt;Docker-compose&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ec2-user]$ sudo yum update -y
[ec2-user]$ sudo yum install -y docker
[ec2-user]$ sudo service docker start
[ec2-user]$ sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
[ec2-user]$ sudo chmod +x /usr/local/bin/docker-compose

[ec2-user]$ docker --version
Docker version 20.10.23, build 7155243

[ec2-user]$ docker-compose --version
Docker Compose version v2.18.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the &lt;code&gt;ec2-user&lt;/code&gt; to the docker &lt;code&gt;group&lt;/code&gt; so that you can use it without running the Docker command with sudo :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ec2-user]$ sudo usermod -a -G docker ec2-user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let’s generate the SSH KEY&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ec2-user]$ ssh-keygen -t rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Please! save the Key without setting any password&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, copy the public key into the authorized_keys file and set the appropriate authorizations.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ec2-user]$ cat ~/.ssh/id_rsa.pub 
# Copy this public key into ~/.ssh/authorized_keys
[ec2-user]$ vi ~/.ssh/authorized_keys
# After adding the public key into authorized_key then exits  from vi text editor 
# Then add these commands are used to change the permissions of these files 
[ec2-user]$ chmod 600 ~/.ssh/authorized_keys
[ec2-user]$ chmod 600 ~/.ssh/id_rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, copy the contents of the private key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ec2-user]$ cat ~/.ssh/id_rsa

# Copy the private key somewhere for later use
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. Automating Deployment with GitHub Actions to AWS EC2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is an integrated continuous integration and continuous deployment (CI / CD) platform provided by GitHub .&lt;/p&gt;
&lt;p&gt;Since our workflow will use GitHub’s core features (GitHub packages), GitHub requires you to create an access token, which will be used as a GitHub user ID for all your interactions.&lt;/p&gt;
&lt;p&gt;Go to the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/settings/tokens&quot;&gt;Personal access tokens area&lt;/a&gt; in the Developer settings of your GitHub profile and click Generate new token.&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15261576398792202104&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To configure &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt;, start by adding a new directory called &lt;code&gt;.github&lt;/code&gt; to the root of your project, then within this directory, add another directory called &lt;code&gt;workflows&lt;/code&gt;. &lt;br&gt;
Now to configure the workflow, which is a set of one or more jobs, create a new file in the workflows directory called &lt;code&gt;main.yml&lt;/code&gt; . &lt;br&gt;
You can also run the following command in your root directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir .github &amp;amp;&amp;amp; cd .github
mkdir workflows &amp;amp;&amp;amp; cd workflows
touch main.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;main.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Continuous Integration and Delivery

on:
  push:
    branches: [main]

env:
  #
  WEBSITE_IMAGE: ghcr.io/$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')/website
  NGINX_IMAGE: ghcr.io/$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')/nginx
  REGISTRY: ghcr.io

jobs:
  build:
    name: Build the Docker Image
    runs-on: ubuntu-latest

    steps:
      - name: checkout main
        uses: actions/checkout@v3

      - name: Set environment variables to .env
        run: |
          echo &quot;WEBSITE_IMAGE=$(echo ${{env.WEBSITE_IMAGE}} )&quot; &amp;gt;&amp;gt; $GITHUB_ENV
          echo &quot;NGINX_IMAGE=$(echo ${{env.NGINX_IMAGE}} )&quot; &amp;gt;&amp;gt; $GITHUB_ENV
      - name: Log in to GitHub Packages
        env:
          PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
        run: echo ${PERSONAL_ACCESS_TOKEN} | docker login ghcr.io -u ${{ secrets.NAMESPACE }} --password-stdin

      - name: Pull images
        run: |
          docker pull ${{ env.WEBSITE_IMAGE }} || true
          docker pull ${{ env.NGINX_IMAGE }} || true
      - name: Build images
        run: |
          docker compose -f docker-compose.ci.yml build
      - name: Push images
        run: |
          docker push ${{ env.WEBSITE_IMAGE }}
          docker push ${{ env.NGINX_IMAGE }}
  checking-secrets:
    name: Checking secrets
    runs-on: ubuntu-latest
    needs: build
    outputs:
      secret_key_exists: ${{steps.check_secrets.outputs.defined}}
    steps:
      - name: Check for Secrets availabilities
        id: check_secrets
        shell: bash
        run: |
          if [[ -n &quot;${{ secrets.PRIVATE_KEY }}&quot; &amp;amp;&amp;amp; -n &quot;${{secrets.AWS_EC2_IP_ADDRESS}}&quot; &amp;amp;&amp;amp; -n &quot;${{secrets.AWS_HOST_USER}}&quot; ]]; then
            echo &quot;defined=true&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT;
          else
            echo &quot;defined=false&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT;
          fi
  deploy:
    name: Deploy to AWS EC2
    runs-on: ubuntu-latest
    needs: checking-secrets
    if: needs.checking-secrets.outputs.secret_key_exists == 'true'
    steps:
      - name: Checkout main
        uses: actions/checkout@v3

      - name: Add environment variables to .env
        run: |
          echo WEBSITE_IMAGE=${{ env.WEBSITE_IMAGE }} &amp;gt;&amp;gt; .env
          echo NGINX_IMAGE=${{ env.NGINX_IMAGE }} &amp;gt;&amp;gt; .env
          echo NAMESPACE=${{ secrets.NAMESPACE }} &amp;gt;&amp;gt; .env
          echo PERSONAL_ACCESS_TOKEN=${{ secrets.PERSONAL_ACCESS_TOKEN }} &amp;gt;&amp;gt; .env
      - name: Add the private SSH key to the ssh-agent
        env:
          SSH_AUTH_SOCK: /tmp/ssh_agent.sock
        run: |
          mkdir -p ~/.ssh
          ssh-agent -a $SSH_AUTH_SOCK &amp;gt; /dev/null
          ssh-keyscan github.com &amp;gt;&amp;gt; ~/.ssh/known_hosts
          ssh-add - &amp;lt;&amp;lt;&amp;lt; &quot;${{ secrets.PRIVATE_KEY }}&quot;
      - name: Deploy images on AWS EC2
        env:
          SSH_AUTH_SOCK: /tmp/ssh_agent.sock

        run: |
          scp -o StrictHostKeyChecking=no -r ./.env ./docker-compose.prod.yml ${{secrets.AWS_HOST_USER}}@${{ secrets.AWS_EC2_IP_ADDRESS }}:
          ssh -o StrictHostKeyChecking=no ${{secrets.AWS_HOST_USER}}@${{ secrets.AWS_EC2_IP_ADDRESS }} &amp;lt;&amp;lt; EOF
              docker-compose down --rmi all -v 
              docker login ghcr.io -u ${{secrets.NAMESPACE}} -p ${{secrets.PERSONAL_ACCESS_TOKEN}}
              docker pull $WEBSITE_IMAGE
              docker pull $NGINX_IMAGE 
              docker-compose --env-file=.env -f docker-compose.prod.yml up -d
              docker logout
          EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the complete &lt;a rel=&quot;nofollow&quot; href=&quot;https://gist.github.com/kilamaelie/d08bd6493e8b4a430bf5ca2f166ac6ad&quot;&gt;gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, in the &lt;code&gt;main.yml&lt;/code&gt; we define :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;set the environment variables&lt;/li&gt;
&lt;li&gt;Three jobs to run:&lt;br&gt;
 a. build job consist of:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Set various environment variables&lt;/li&gt;
&lt;li&gt;Login in the Github package by using the PERSONAL_ACCESS_TOKEN created&lt;/li&gt;
&lt;li&gt;Pull images for caching, build the images and push to Github Registry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  b. checking-secret consist of :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checking if the deploy variables are existing&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Notes about the secrets&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;secrets.PERSONAL_ACCESS_TOKEN&lt;/li&gt;
&lt;li&gt;secrets.NAMESPACE&lt;/li&gt;
&lt;li&gt;secrets.PRIVATE_KEY&lt;/li&gt;
&lt;li&gt;secret.AWS_EC2_IP_ADDRESS&lt;/li&gt;
&lt;li&gt;secrets.AWS_HOST_USER&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the secrets need to be set in your repository’s &lt;a rel=&quot;nofollow&quot; href=&quot;https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets&quot;&gt;secrets&lt;/a&gt; (Settings &amp;gt; Secrets) and use your Github username or your organization name as your NAMESPACE and your personal access token as PERSONAL_ACCESS_TOKEN .&lt;/p&gt;
&lt;p&gt;  c. deploy job consist of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add the environment variable to .env file which will be created on the AWS EC2 instance.&lt;/li&gt;
&lt;li&gt;Add the private key to ssh-agent for password less.&lt;/li&gt;
&lt;li&gt;Deploy the build the images on AWS EC2 instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you are done, commit and push your code to the Github to trigger the workflow to run, so now you should see the images in the Github Packages.&lt;/p&gt;
&lt;p&gt;Please make sure your on &lt;code&gt;main branch&lt;/code&gt; because the workflow’s can only trigger on the &lt;code&gt;main&lt;/code&gt; branch other you can it to your preferences.&lt;/p&gt;
&lt;p&gt;Workflow successfully finished :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17531408084622768069&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once all our jobs have been executed, navigate to the IP of your instance, and you should see the React application running :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=12091261597270377422&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; And there you have it, this guide has equipped you with the skills you need to successfully build, deploy and automate ReactJS applications using Docker, NGINX and GitHub Actions on AWS EC2.&lt;/p&gt;
&lt;p&gt;If you would like to improve your skills, click on the following link on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.patreon.com/kilamaelie/shop/master-react-app-deployment-aws-ec2-53-1089307?utm_medium=clipboard_copy&amp;amp;utm_source=copyLink&amp;amp;utm_campaign=productshare_fan&amp;amp;utm_content=join_link&quot;&gt;Master React App Deployment: AWS EC2, Route 53, SSL &amp;amp; GitHub Actions,A comprehensive manual which walks you through the deployment process, leveraging the power of Docker, AWS EC2, Route 53, SSL encryption and GitHub actions to build a robust CI/CD pipeline&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep exploring, stay curious and keep building on this foundation to create even more sophisticated and powerful projects. Happy coding!&lt;/p&gt;
&lt;p&gt;Thanks for reading through and I hope you liked what you read here. Feel free to connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/kilamaelie/&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/kilamaelie&quot;&gt;twitter&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kilamaelie&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/3479/deploy-a-reactjs-app-to-aws-ec2-with-docker-nginx-and-automate-with-github-actions-part-two</guid>
<pubDate>Wed, 25 Jun 2025 17:01:06 +0000</pubDate>
</item>
<item>
<title>Securing Your VPS: Basic Firewall, SSH, and SSL Setup Guide</title>
<link>https://coderlegion.com/3268/securing-your-vps-basic-firewall-ssh-and-ssl-setup-guide</link>
<description>&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-body&quot;&gt;A Simple Guide to Make Your VPS Safer — Even If You're Just Starting Out&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Your &lt;strong&gt;VPS (Virtual Private Server)&lt;/strong&gt; is like your home on the internet — and just like a home, it needs locks, fences, and security cameras to stay safe. This guide will walk you through &lt;strong&gt;three critical steps&lt;/strong&gt; to secure your VPS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up a basic firewall&lt;/li&gt;
&lt;li&gt;Secure -SSH access&lt;/li&gt;
&lt;li&gt;Install SSL for HTTPS&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-set-up-a-basic-firewall-with-ufw-uncomplicated-firewall-&quot;&gt;Step 1: Set Up a Basic Firewall with UFW (Uncomplicated Firewall)&lt;/h3&gt;
&lt;p&gt;A firewall controls what kind of traffic can reach your VPS. Let’s use &lt;strong&gt;UFW&lt;/strong&gt;, a beginner-friendly firewall for Ubuntu/Debian systems.&lt;/p&gt;
&lt;h4 id=&quot;-install-ufw&quot;&gt;✅ Install UFW&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt update
sudo apt install ufw
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-allow-ssh-so-you-don-t-lock-yourself-out-&quot;&gt;✅ Allow SSH (so you don’t lock yourself out)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow OpenSSH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're using a non-standard SSH port (e.g., 2222), allow that too:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow 2222/tcp
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-allow-http-and-https&quot;&gt;✅ Allow HTTP and HTTPS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow 80
sudo ufw allow 443
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-enable-the-firewall&quot;&gt;✅ Enable the Firewall&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw enable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw status
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-red&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Pro Tip:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Only open ports that your app or server needs!&lt;/span&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-secure-ssh-access&quot;&gt;Step 2: Secure SSH Access&lt;/h3&gt;
&lt;p&gt;SSH is how you remotely access your server. But by default, it’s a bit too open.&lt;/p&gt;
&lt;h4 id=&quot;-change-the-default-ssh-port-optional-but-adds-extra-protection-&quot;&gt;✅ Change the Default SSH Port (optional but adds extra protection)&lt;/h4&gt;
&lt;p&gt;Edit the SSH config file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find and change:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Port 22
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Port 2222
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then restart SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-disable-root-login-via-ssh&quot;&gt;✅ Disable Root Login via SSH&lt;/h4&gt;
&lt;p&gt;Still in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;, find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PermitRootLogin yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PermitRootLogin no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart SSH again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-use-ssh-keys-instead-of-passwords&quot;&gt;✅ Use SSH Keys Instead of Passwords&lt;/h4&gt;
&lt;p&gt;From your &lt;strong&gt;local machine&lt;/strong&gt;, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t rsa -b 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then copy the key to your VPS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-copy-id username@your-server-ip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disable password authentication (optional but recommended):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PasswordAuthentication no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-install-free-ssl-using-let-s-encrypt-with-certbot-&quot;&gt;Step 3: Install Free SSL Using Let’s Encrypt (with Certbot)&lt;/h3&gt;
&lt;p&gt;SSL (HTTPS) encrypts your website traffic. Let’s Encrypt makes it easy and free.&lt;/p&gt;
&lt;h4 id=&quot;-install-certbot&quot;&gt;✅ Install Certbot&lt;/h4&gt;
&lt;p&gt;For Nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install certbot python3-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Apache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install certbot python3-certbot-apache
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-issue-an-ssl-certificate&quot;&gt;✅ Issue an SSL Certificate&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot --nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or for Apache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot --apache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the prompt and choose to redirect HTTP to HTTPS.&lt;/p&gt;
&lt;h4 id=&quot;-auto-renew-ssl&quot;&gt;✅ Auto-Renew SSL&lt;/h4&gt;
&lt;p&gt;Let’s Encrypt certificates expire every 90 days. Set up auto-renewal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl list-timers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Certbot typically installs a timer, but you can test renewal with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot renew --dry-run
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-body&quot;&gt;Security doesn't have to be scary.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;With just a few commands, you've:&lt;/p&gt;
&lt;p&gt;✅ Protected your SSH login&lt;br&gt;
✅ Closed unnecessary network doors&lt;br&gt;
✅ Encrypted web traffic with HTTPS&lt;/p&gt;
&lt;p&gt;These are &lt;strong&gt;must-do steps&lt;/strong&gt; for anyone hosting websites, APIs, or apps on a VPS — even beginners.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;bonus-tools-for-extra-security-optional-&quot;&gt;Bonus Tools for Extra Security (Optional)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fail2Ban&lt;/strong&gt;: Automatically blocks brute-force attempts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ModSecurity&lt;/strong&gt;: Web application firewall&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditd&lt;/strong&gt;: For logging and monitoring server changes&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://help.ubuntu.com/community/UFW&quot;&gt;UFW Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.ssh.com/academy/ssh/security-best-practices&quot;&gt;SSH Hardening Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://letsencrypt.org&quot;&gt;Let’s Encrypt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Basic Commands</category>
<guid isPermaLink="true">https://coderlegion.com/3268/securing-your-vps-basic-firewall-ssh-and-ssl-setup-guide</guid>
<pubDate>Sat, 07 Jun 2025 17:01:23 +0000</pubDate>
</item>
<item>
<title>Nginx Configuration Tips for Secure Communication: Enabling mTLS and checking client fingerprint</title>
<link>https://coderlegion.com/1198/nginx-configuration-tips-secure-communication-enabling-mtls-checking-client-fingerprint</link>
<description>&lt;p&gt;This paper is intended for beginners looking to enhance web security by configuring mutual Transport Layer Security (mTLS) in Nginx and verifying the client’s certificate fingerprint or securing specific URLs with mTLS. The paper provides a comprehensive guide on how to implement these security measures in Nginx, offering step-by-step instructions and practical advice. The resulting Nginx configuration is available at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stjam/mtls_demo/blob/master/nginx.conf&quot;&gt;https://github.com/stjam/mtls_demo/blob/master/nginx.conf&lt;/a&gt;. By following this guide, readers can improve the security of their web applications and prevent unauthorized access to sensitive data.&lt;/p&gt;
&lt;p&gt;A demo project has been prepared and is available at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stjam/mtls_demo/tree/master&quot;&gt;https://github.com/stjam/mtls_demo/tree/master&lt;/a&gt;. The project is a simple web application with two endpoints: /api/private, which echoes the headers of requests, and /api/public, which returns the current time. All traffic is routed through Nginx, which proxies requests to the backend.&lt;/p&gt;
&lt;p&gt;I have used the following software versions to prepare this tutorial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maven 3.8.7&lt;/li&gt;
&lt;li&gt;Docker Desktop 4.16.1&lt;/li&gt;
&lt;li&gt;Java 19&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LibreSSL 3.3.6&lt;br&gt;
To build backend you can use the next set of commands&lt;/p&gt;
&lt;p&gt; ./mvnw package -Pnative -Dquarkus.native.container-build=true&lt;/p&gt;
&lt;p&gt; docker build -f src/main/docker/Dockerfile.native -t quarkus/mtls_demo&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To run everything, you need to use docker-compose, which I have included here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stjam/mtls_demo/blob/master/docker-compose.yaml&quot;&gt;https://github.com/stjam/mtls_demo/blob/master/docker-compose.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s make access to our backend more secure. Initially, we have the following nginx.conf file, which simply proxies requests to our backend service.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;events {
    worker_connections 100;
    multi_accept on;
}

http {
    server {
      listen 80;
      location / {
          proxy_pass http://quarkus:8080;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          }
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And our private API is not secure, as we can directly call it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3996514195602148219&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I recommend changing the content of &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stjam/mtls_demo/blob/master/nginx.conf&quot;&gt;https://github.com/stjam/mtls_demo/blob/master/nginx.conf&lt;/a&gt; with every snippet of configuration that I’ve put here, as it will make understanding the key steps of this tutorial better.&lt;/p&gt;
&lt;p&gt;First of all, we need to create a server certificate and enable SSL in Nginx. You can skip certificate generation and use the certificate set I’ve put in the Github repo.&lt;/p&gt;
&lt;p&gt;To generate a server certificate, use the following command. You can read more about the parameters of this command in &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@stjamlb/openssl-made-easy-a-practical-guide-to-generating-and-signing-x-509-certificates-da85e8f2db3e&quot;&gt;https://medium.com/@stjamlb/openssl-made-easy-a-practical-guide-to-generating-and-signing-x-509-certificates-da85e8f2db3e&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For convince you can combine key and server certificate in one file&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat server.key server.crt &amp;gt; server.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modified nginx.conf:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;events {
    worker_connections 100;
    multi_accept on;
}

http {
    server {
      listen 443 ssl;
      ssl_certificate /etc/nginx/server.pem;
      ssl_certificate_key /etc/nginx/server.pem;

      location / {
          proxy_pass http://quarkus:8080;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          }
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we listen on port 443, and our clients should use HTTPS to call our service. We’ve also added paths to our server certificate and key. If we call without HTTPS, we get the following error:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7490634578328432779&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;After modifying our request to call via HTTPS, we get a successful response.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1013232928561400026&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When configuring an HTTPS connection, it’s important to verify the connection between the client and server. This can be done using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl s_client -connect 127.0.0.1:443 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will connect to the server on port 443 and print out detailed information about the SSL/TLS handshake process, including the server’s certificate details, the supported cipher suites, and the SSL/TLS session parameters:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now that the traffic between our client and server is encrypted, clients can verify the server certificate. However, in some cases, a TLS connection alone may not be sufficient. If you need to verify the client’s certificate on the server side, you should configure mutual TLS (mTLS)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;events {
    worker_connections 100;
    multi_accept on;
}

http {
    server {
      listen 443 ssl;
      ssl_certificate /etc/nginx/server.pem;
      ssl_certificate_key /etc/nginx/server.pem;
      ssl_client_certificate /etc/nginx/server.crt;
      ssl_verify_client on;

      location / {
          proxy_pass http://quarkus:8080;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          }
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have added the option ‘ssl_verify_client on;’ and the path to the client certificate ‘ssl_client_certificate’ in our Nginx configuration. This means that Nginx will now require a client certificate for a connection. However, we encountered an error when trying to establish a connection using OpenSSL. Let’s fix this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11253580311824686165&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;we need client certificate. I should notice that both the client and server need to have certificates that are signed by the same Certificate Authority (CA). This ensures that both parties can trust each other’s identity during the SSL/TLS handshake. Without this trust relationship, the client and server will not be able to establish a secure connection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. openssl req -new -newkey rsa:2048 -nodes -keyout client.key -out client.csr 
 
2. openssl x509 -req -days 365 -in client.csr -CA server.crt -CAkey server.key -CAcreateserial -out client.crt  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first command generates a certificate signing request (CSR) for the client certificate, and the second command signs the CSR with the CA key to generate the client certificate. After executing these commands, you will have a client.crt file containing the client certificate.&lt;/p&gt;
&lt;p&gt;Now we can check the OpenSSL connection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl s_client -connect 127.0.0.1:443 -key client.key -cert client.crt -CAfile server.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After execution you should see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13569110829465715162&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And now, in order to call our private API, we must provide the -cert and -key options with the path to the client.key and client certificate.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --cert client.crt --key client.key https://127.0.0.1:443/api/private --insecure  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything is working as expected, but we need to remember that we have a public endpoint that doesn’t require client certificate verification. Let’s configure Nginx to only check the certificate for the /api/private endpoint. We just need to add a new location and make ssl_verify_client optional. We also add an if condition to check if the ssl_connection was successful or not. If it was not successful, we return a 403 error. Otherwise, we proxy the request to our backend.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;events {
    worker_connections 100;
    multi_accept on;
}

http {
    server {
      listen 443 ssl;
      ssl_certificate /etc/nginx/server.pem;
      ssl_certificate_key /etc/nginx/server.pem;
      ssl_verify_client optional;
      ssl_client_certificate /etc/nginx/server.crt;

      location / {
          proxy_pass http://quarkus:8080;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          }

      location /api/private {
                if ($ssl_client_verify != SUCCESS) {
                    return 403;
                }
                proxy_pass http://quarkus:8080;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
        }
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we only need to provide a certificate with our request if we are accessing the /api/private endpoint, as the /api/public endpoint is available directly without any additional security measures. In this case, we have two options: we can either check the client certificate fingerprint or the client CN. To find the fingerprint using openssl, we can use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -noout -fingerprint -sha1 -inform pem -in client.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adding the fingerprint check to our configuration, our updated nginx configuration looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;events {
    worker_connections 100;
    multi_accept on;
}

http {
    server {
      listen 443 ssl;
      ssl_certificate /etc/nginx/server.pem;
      ssl_certificate_key /etc/nginx/server.pem;
      ssl_verify_client optional;
      ssl_client_certificate /etc/nginx/server.crt;

      location / {
          proxy_pass http://quarkus:8080;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          }

      location /api/private {
                if ($ssl_client_verify != SUCCESS) {
                                    return 403;
                            }

                if ($ssl_client_fingerprint != 7dc7ec3379c699dc2dcf8a44e67acae5ae09e06f) {
                        return 403;
                    }



                proxy_pass http://quarkus:8080;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
        }
      }
      log_format main '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
                          '$status $body_bytes_sent &quot;$http_referer&quot; '
                          '&quot;$http_user_agent&quot; &quot;$ssl_client_fingerprint&quot; &quot;$ssl_client_s_dn&quot;';
      access_log /var/log/nginx/access.log main;

  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I should note that when checking the fingerprint, you must provide the SHA1 fingerprint without any colons. Additionally, I’ve included an example of how to enrich the information in the Nginx access.log with useful details such as the client certificate fingerprint or client certificate CN.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We have demonstrated the steps required to configure mutual TLS (mTLS) in Nginx, and explained how to verify the connection using OpenSSL.&lt;/p&gt;
&lt;p&gt;After that, we worked with a location block in our Nginx configuration to check client certificates only for a specific endpoint (/api/private), and not for the public endpoint (/api/public).&lt;/p&gt;
&lt;p&gt;Finally, we have discussed how to enrich the information in the Nginx access log with useful details such as client certificate fingerprints or CN. By following these steps, we can ensure that our communication is secure and only authorized parties can access our API endpoints. This dialog provides a comprehensive guide to configuring mTLS in Nginx for secure communication between the client and server.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/1198/nginx-configuration-tips-secure-communication-enabling-mtls-checking-client-fingerprint</guid>
<pubDate>Fri, 21 Feb 2025 20:38:01 +0000</pubDate>
</item>
</channel>
</rss>