<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lia</title>
    <description>The latest articles on DEV Community by Lia (@lialiago).</description>
    <link>https://dev.to/lialiago</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2050794%2F3814506f-596e-4892-bf72-747df3d307fe.jpeg</url>
      <title>DEV Community: Lia</title>
      <link>https://dev.to/lialiago</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lialiago"/>
    <language>en</language>
    <item>
      <title>LDAP and Active Directory Login for Self-Hosted Apps</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Tue, 18 Aug 2026 03:53:34 +0000</pubDate>
      <link>https://dev.to/lialiago/ldap-and-active-directory-login-for-self-hosted-apps-3jck</link>
      <guid>https://dev.to/lialiago/ldap-and-active-directory-login-for-self-hosted-apps-3jck</guid>
      <description>&lt;p&gt;If your users already live in an enterprise directory — Active Directory, OpenLDAP, Apache Directory, or FreeIPA — then the cleanest login for a self-hosted app is the account they already have. Nobody wants to remember a second password for an internal tool when they log into the company directory every morning.&lt;/p&gt;

&lt;p&gt;SafeLine WAF speaks LDAP, so it can front your apps and let users sign in with their existing directory accounts. No directory code in your app, no separate user database. Here's the full configuration, straight from the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How LDAP fits the picture
&lt;/h2&gt;

&lt;p&gt;LDAP (Lightweight Directory Access Protocol) is the protocol for reading from a distributed directory service — the thing that stores user accounts, organizational structure, and permissions. It's also the foundation of most single-sign-on infrastructure: one set of credentials, many systems.&lt;/p&gt;

&lt;p&gt;When a WAF fronts your app, it can query your directory during login, authenticate the user, and only then let them reach the origin. That means your enterprise directory becomes the login for everything the WAF protects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the LDAP configuration
&lt;/h2&gt;

&lt;p&gt;In the SafeLine console, go to &lt;strong&gt;Auth → Settings&lt;/strong&gt;, click &lt;strong&gt;Third-Party Login&lt;/strong&gt;, and select &lt;strong&gt;LDAP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll fill in five parameters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LDAP Server URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where the directory lives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bind DN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The account used to connect to the directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bind Password&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Password for that bind account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User baseDN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where in the directory tree to start searching for users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query Condition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The filter that matches a user entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: The parameters in detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LDAP Server URL
&lt;/h3&gt;

&lt;p&gt;Format is &lt;code&gt;ldap://hostname:port&lt;/code&gt; or &lt;code&gt;ldaps://hostname:port&lt;/code&gt; for SSL. Standard ports are &lt;strong&gt;389&lt;/strong&gt; (plain) and &lt;strong&gt;636&lt;/strong&gt; (SSL). For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ldap&lt;/span&gt;://&lt;span class="n"&gt;ldap&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;:&lt;span class="m"&gt;389&lt;/span&gt;   &lt;span class="c"&gt;# standard connection
&lt;/span&gt;&lt;span class="n"&gt;ldaps&lt;/span&gt;://&lt;span class="n"&gt;ldap&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;:&lt;span class="m"&gt;636&lt;/span&gt;  &lt;span class="c"&gt;# SSL encrypted
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bind DN
&lt;/h3&gt;

&lt;p&gt;The account used to bind to the LDAP server. It needs enough permission to query user information. A couple of reference shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Directory: &lt;code&gt;cn=Administrator,cn=Users,dc=domain,dc=com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OpenLDAP: &lt;code&gt;cn=admin,dc=example,dc=com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs recommend a dedicated service account with read-only permissions rather than a full administrator account.&lt;/p&gt;

&lt;h3&gt;
  
  
  User baseDN
&lt;/h3&gt;

&lt;p&gt;This is the node in the directory tree where user searches begin — it affects both performance and search scope. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Directory: &lt;code&gt;cn=Users,dc=domain,dc=com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OpenLDAP: &lt;code&gt;ou=People,dc=example,dc=com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query Condition
&lt;/h3&gt;

&lt;p&gt;A filter expression used to match user entries, where &lt;code&gt;%s&lt;/code&gt; is a placeholder for the username the visitor types. The basic form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(&amp;amp;(objectClass=person)(uid=%s))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches entries where &lt;code&gt;objectClass&lt;/code&gt; is &lt;code&gt;person&lt;/code&gt; and &lt;code&gt;uid&lt;/code&gt; equals the entered username. If a user enters &lt;code&gt;john&lt;/code&gt;, the filter becomes &lt;code&gt;(&amp;amp;(objectClass=person)(uid=john))&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Reference configs for common directories
&lt;/h2&gt;

&lt;p&gt;The docs include ready-to-adapt examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Active Directory&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;LDAP&lt;/span&gt; &lt;span class="err"&gt;Server&lt;/span&gt; &lt;span class="py"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ldap://ad.company.com:389&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;DN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;cn=LDAPService,cn=Users,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;(service account password)&lt;/span&gt;
&lt;span class="err"&gt;User&lt;/span&gt; &lt;span class="py"&gt;baseDN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;cn=Users,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Query&lt;/span&gt; &lt;span class="py"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=user)(sAMAccountName=%s))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OpenLDAP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;LDAP&lt;/span&gt; &lt;span class="err"&gt;Server&lt;/span&gt; &lt;span class="py"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ldap://openldap.company.com:389&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;DN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;cn=admin,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;(admin password)&lt;/span&gt;
&lt;span class="err"&gt;User&lt;/span&gt; &lt;span class="py"&gt;baseDN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;ou=People,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Query&lt;/span&gt; &lt;span class="py"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=person)(uid=%s))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FreeIPA&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;LDAP&lt;/span&gt; &lt;span class="err"&gt;Server&lt;/span&gt; &lt;span class="py"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ldap://ipa.company.com:389&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;DN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;uid=admin,cn=users,cn=accounts,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Bind&lt;/span&gt; &lt;span class="py"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;(admin password)&lt;/span&gt;
&lt;span class="err"&gt;User&lt;/span&gt; &lt;span class="py"&gt;baseDN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;cn=users,cn=accounts,dc=company,dc=com&lt;/span&gt;
&lt;span class="err"&gt;Query&lt;/span&gt; &lt;span class="py"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=person)(uid=%s))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the login flow looks like
&lt;/h2&gt;

&lt;p&gt;After you enable LDAP for an application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visitors are prompted to authenticate with their LDAP account.&lt;/li&gt;
&lt;li&gt;They log in with their directory credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New users&lt;/strong&gt; need verification by a SafeLine administrator after their first authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified users&lt;/strong&gt; are authorized and can log in on later visits without re-verification; &lt;strong&gt;unverified users&lt;/strong&gt; are blocked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Users can't log in even though the connection works?
&lt;/h3&gt;

&lt;p&gt;Check the &lt;strong&gt;User baseDN&lt;/strong&gt; (does the user actually sit under it?) and the &lt;strong&gt;Query Condition&lt;/strong&gt; — the &lt;code&gt;%s&lt;/code&gt; placeholder, the attribute names (&lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;sAMAccountName&lt;/code&gt;, etc.), and the outermost parentheses all need to be correct. Also confirm the account isn't locked or disabled in the directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is the connection failing?
&lt;/h3&gt;

&lt;p&gt;Check the server URL format (protocol, hostname, port), that the SafeLine server can reach the LDAP server on the network, the bind account credentials, and — for &lt;code&gt;ldaps://&lt;/code&gt; — that the certificate is valid or trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;%s&lt;/code&gt; mean in the query condition?
&lt;/h3&gt;

&lt;p&gt;It's replaced with the username entered at login. &lt;code&gt;(&amp;amp;(objectClass=person)(uid=%s))&lt;/code&gt; with input &lt;code&gt;john&lt;/code&gt; becomes &lt;code&gt;(&amp;amp;(objectClass=person)(uid=john))&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I filter by Active Directory security group?
&lt;/h3&gt;

&lt;p&gt;Yes — add a membership condition, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(&amp;amp;(objectClass=user)(sAMAccountName=%s)(memberOf=CN=AllowedGroup,OU=Groups,DC=company,DC=com))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  My query condition has syntax errors — any tips?
&lt;/h3&gt;

&lt;p&gt;LDAP filters use prefix notation, so make sure parentheses are matched and the &lt;code&gt;attribute=value&lt;/code&gt; syntax is right. The docs suggest validating with an online LDAP filter syntax checker.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Do your internal apps already authenticate against your directory — or are users juggling a separate set of credentials for each tool?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to protect your sites without paying for a cloud WAF?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/chaitin/SafeLine" rel="noopener noreferrer"&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 href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://demo.waf.chaitin.com:9443/statistics" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>tutorial</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>OIDC Authentication with Keycloak (and Auth0, Okta, Azure AD)</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Tue, 18 Aug 2026 03:52:16 +0000</pubDate>
      <link>https://dev.to/lialiago/oidc-authentication-with-keycloak-and-auth0-okta-azure-ad-140g</link>
      <guid>https://dev.to/lialiago/oidc-authentication-with-keycloak-and-auth0-okta-azure-ad-140g</guid>
      <description>&lt;p&gt;If your team already runs an identity provider, the last thing you want is a second login system. You want your apps to trust the identity system you already have — whether that's Keycloak, Auth0, Okta, or Azure AD. OpenID Connect (OIDC) is the protocol that makes that possible, and a self-hosted WAF can speak it for you.&lt;/p&gt;

&lt;p&gt;SafeLine supports OIDC out of the box. Instead of adding an OIDC client library to each app, you configure the identity provider once in the WAF, and it handles the redirect and the login gate in front of your applications. Here's the setup, straight from the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What OIDC actually is
&lt;/h2&gt;

&lt;p&gt;OpenID Connect is an identity layer built on top of OAuth 2.0. It lets a client application verify who a user is, based on the authentication result from an authorization server — and get back basic user information — without the client ever handling the user's credentials directly.&lt;/p&gt;

&lt;p&gt;For a WAF, that's a clean fit: the WAF fronts your app, redirects unauthenticated visitors to your identity provider, and only lets approved users through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Prepare your OIDC provider
&lt;/h2&gt;

&lt;p&gt;The docs walk through Keycloak as the reference example, since it's a common self-hosted choice. Here's how to get the parameters SafeLine needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to the Keycloak admin console.&lt;/li&gt;
&lt;li&gt;Select or create a &lt;strong&gt;Realm&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the left menu, click &lt;strong&gt;Clients → Create&lt;/strong&gt; to create a new client.&lt;/li&gt;
&lt;li&gt;Fill in a &lt;strong&gt;Client ID&lt;/strong&gt; — a unique identifier for the client.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Settings&lt;/strong&gt; tab:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valid Redirect URIs&lt;/strong&gt; — add the application's callback address, for example &lt;code&gt;https://example.com/.safeline/auth/api/callback/oidc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Origins&lt;/strong&gt; — add the application address, for example &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save, then switch to the &lt;strong&gt;Credentials&lt;/strong&gt; tab and copy the &lt;strong&gt;Client Secret&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key things to record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OIDC Discovery URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://{keycloak-host}/realms/{realm-name}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The client ID you created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Secret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The secret from the Credentials tab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The callback path is fixed: append &lt;code&gt;/.safeline/auth/api/callback/oidc&lt;/code&gt; to your application URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Configure SafeLine
&lt;/h2&gt;

&lt;p&gt;In the SafeLine console, go to &lt;strong&gt;Auth → Settings → Login Configuration&lt;/strong&gt;, find &lt;strong&gt;Third-Party Login&lt;/strong&gt;, and select &lt;strong&gt;OIDC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fill in the form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OIDC Server URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your provider's discovery URL. SafeLine appends &lt;code&gt;.well-known/openid-configuration&lt;/code&gt; automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;From your OIDC provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Secret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;From your OIDC provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Title Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shown on the blocking page — defaults to "Sign In - OIDC"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Proxy to Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check if your requests go through a proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto Merge Account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatically match incoming logins to existing accounts by email or phone number&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click &lt;strong&gt;Submit&lt;/strong&gt; to save the binding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Enable OIDC on the application
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Applications&lt;/strong&gt; and pick the app you want to protect.&lt;/li&gt;
&lt;li&gt;In that app's &lt;strong&gt;AUTH → Login Methods&lt;/strong&gt; configuration, enable &lt;strong&gt;OIDC&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Save.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the login flow looks like
&lt;/h2&gt;

&lt;p&gt;Once OIDC is enabled for an application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A visitor opens the app and sees the OIDC login option.&lt;/li&gt;
&lt;li&gt;Clicking it redirects them to your identity provider's login page.&lt;/li&gt;
&lt;li&gt;After authenticating there, they're redirected back to SafeLine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-time users&lt;/strong&gt; need to be approved by a SafeLine administrator before they can access the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved users&lt;/strong&gt; get authorized and don't need another review for later visits; &lt;strong&gt;unapproved users&lt;/strong&gt; are blocked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Managing users
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;AUTH → User Management&lt;/strong&gt; you can view which users are linked, review new users, manage access permissions, and manually merge accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can't redirect to the OIDC provider after integration?
&lt;/h3&gt;

&lt;p&gt;Check that the OIDC discovery URL is correct and that it's reachable from a browser and returns valid JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Callback fails after the user authenticates with the provider?
&lt;/h3&gt;

&lt;p&gt;Confirm the callback address is configured correctly — protocol (&lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt;), domain, and path all need to match.&lt;/p&gt;

&lt;h3&gt;
  
  
  What scopes does SafeLine request by default?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;openid&lt;/code&gt;, &lt;code&gt;profile&lt;/code&gt;, and &lt;code&gt;email&lt;/code&gt; (optional, used to link accounts), plus &lt;code&gt;phone&lt;/code&gt; (optional, for account linking).&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to write OIDC code in my app?
&lt;/h3&gt;

&lt;p&gt;Not for the login gate itself. The WAF handles the redirect flow. You'd only add app code if the application needs the authenticated user's identity — via the auth callback flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which providers are supported?
&lt;/h3&gt;

&lt;p&gt;The docs name Keycloak, Auth0, Okta, and Azure AD, and note the list isn't limited to those — anything speaking standard OIDC should work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you already run Keycloak, Auth0, Okta, or Azure AD — are your internal apps currently trusting it, or are you maintaining a second login system?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to protect your sites without paying for a cloud WAF?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/chaitin/SafeLine" rel="noopener noreferrer"&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 href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://demo.waf.chaitin.com:9443/statistics" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>How to Add GitHub Login to Your Web App with a WAF</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Tue, 18 Aug 2026 03:48:37 +0000</pubDate>
      <link>https://dev.to/lialiago/how-to-add-github-login-to-your-web-app-with-a-waf-41f4</link>
      <guid>https://dev.to/lialiago/how-to-add-github-login-to-your-web-app-with-a-waf-41f4</guid>
      <description>&lt;p&gt;"Sign in with GitHub" is one of the first things developers reach for when an internal tool needs authentication. The usual route is a real engineering task: register a GitHub OAuth app, add an OAuth client library to your codebase, store the &lt;code&gt;ClientID&lt;/code&gt; and &lt;code&gt;ClientSecret&lt;/code&gt;, implement the callback handler, and manage sessions yourself.&lt;/p&gt;

&lt;p&gt;There's a shorter path. If you front your web app with a self-hosted WAF that runs as a reverse proxy, you can let the WAF handle the entire GitHub OAuth flow — before a single request reaches your application code. SafeLine does exactly this. Here's the full walkthrough, straight from the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do GitHub login at the WAF layer?
&lt;/h2&gt;

&lt;p&gt;A WAF already sits in front of your app, so every request passes through it. That makes it a natural place to gate access. Letting the WAF handle GitHub authentication means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No OAuth code in your app.&lt;/strong&gt; You don't add a client library, a callback route, or token storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One place to manage identity.&lt;/strong&gt; Add or remove GitHub login across several apps from one console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works for anything.&lt;/strong&gt; An internal dashboard, a legacy tool, or a static site can all get GitHub login the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the GitHub OAuth app
&lt;/h2&gt;

&lt;p&gt;First you need a GitHub OAuth application to act as the identity source. This is done on GitHub's side, not SafeLine's.&lt;/p&gt;

&lt;p&gt;From the GitHub documentation flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to GitHub and click your profile picture, then open &lt;strong&gt;Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the left sidebar, click &lt;strong&gt;Developer Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OAuth Apps&lt;/strong&gt;, then &lt;strong&gt;New OAuth App&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Fill in the application form:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application name&lt;/strong&gt; — what users see on the authentication page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homepage URL&lt;/strong&gt; — your application's URL, for example &lt;code&gt;https://a.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization callback URL&lt;/strong&gt; — your homepage URL with &lt;code&gt;/.safeline/auth/api/callback/github&lt;/code&gt; appended. For the example above: &lt;code&gt;https://a.com/.safeline/auth/api/callback/github&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once created, GitHub gives you the two values you'll need next: a &lt;strong&gt;ClientID&lt;/strong&gt; and a &lt;strong&gt;ClientSecret&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The callback URL is the one place people get stuck. It's not arbitrary — it's always your Homepage URL plus the fixed path &lt;code&gt;/.safeline/auth/api/callback/github&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Configure SafeLine
&lt;/h2&gt;

&lt;p&gt;Now move to the SafeLine console. Go to &lt;strong&gt;Auth → Settings → Login Configuration&lt;/strong&gt;, find &lt;strong&gt;Third-Party Login&lt;/strong&gt;, and select &lt;strong&gt;GitHub&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fill in the configuration form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ClientID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your GitHub application ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ClientSecret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your GitHub application secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Title Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The name shown on the blocking page — defaults to "Sign In - GitHub"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Proxy to Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check this if your requests need to go through a proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto Merge Account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatically match incoming GitHub logins to existing accounts by email&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click &lt;strong&gt;Submit&lt;/strong&gt; to save the binding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Enable GitHub on the application
&lt;/h2&gt;

&lt;p&gt;Configuring the GitHub source isn't enough — you also have to switch it on for the specific app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Applications&lt;/strong&gt; page and select the application you want to protect.&lt;/li&gt;
&lt;li&gt;In that application's &lt;strong&gt;AUTH&lt;/strong&gt; page, open the &lt;strong&gt;Login Methods&lt;/strong&gt; settings.&lt;/li&gt;
&lt;li&gt;Enable the &lt;strong&gt;GitHub&lt;/strong&gt; option.&lt;/li&gt;
&lt;li&gt;Save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. The app is now gated behind GitHub authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a user tries to log in
&lt;/h2&gt;

&lt;p&gt;Once the AUTH feature is enabled and GitHub is selected as the login method, the flow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When someone visits the app, they're prompted to authenticate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New users&lt;/strong&gt; authenticate, then need to be &lt;strong&gt;reviewed and approved by a SafeLine administrator&lt;/strong&gt; in the console before they can get in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved users&lt;/strong&gt; are authorized for the app and don't need another review — subsequent logins just work after authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unapproved users&lt;/strong&gt; are blocked when they try to access the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approval step is a useful default: it stops an open GitHub login from becoming a backdoor for anyone with a GitHub account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing users after login
&lt;/h2&gt;

&lt;p&gt;On the &lt;strong&gt;AUTH → User Management&lt;/strong&gt; page you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See whether users have linked their GitHub accounts.&lt;/li&gt;
&lt;li&gt;Review new users for approval.&lt;/li&gt;
&lt;li&gt;Manage access permissions per user.&lt;/li&gt;
&lt;li&gt;Manually merge user accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "merge" option is handy when someone has been logging in one way and you want to fold their history into another account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: combine it with SSO
&lt;/h2&gt;

&lt;p&gt;If you want users to authenticate just once and then reach &lt;em&gt;several&lt;/em&gt; applications, GitHub login can feed into SSO:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;AUTH → Settings → SSO&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enable the unified authentication feature.&lt;/li&gt;
&lt;li&gt;Add applications to unified authentication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, a user authenticates once — including through GitHub — and can access every application that's joined SSO.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I have to write any application code for GitHub login?
&lt;/h3&gt;

&lt;p&gt;Not for the login itself. The WAF handles the OAuth redirect and gatekeeping. Your app only needs custom code if it has to &lt;em&gt;know who&lt;/em&gt; logged in — for that you'd use the auth callback flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does a new GitHub user need admin approval?
&lt;/h3&gt;

&lt;p&gt;It's the default safety behavior: first-time users trigger a review so an admin can confirm who gets access. After approval, the same user logs in without further review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use GitHub login on more than one app?
&lt;/h3&gt;

&lt;p&gt;Yes. You configure the GitHub source once, then enable it on each application individually from that app's AUTH → Login Methods settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my traffic goes through a proxy?
&lt;/h3&gt;

&lt;p&gt;Check the &lt;strong&gt;Use Proxy to Access&lt;/strong&gt; option in the GitHub login configuration. That tells SafeLine to route its GitHub calls through a proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does GitHub login work together with other login methods?
&lt;/h3&gt;

&lt;p&gt;Yes. SafeLine supports several login methods (account/password, OIDC, LDAP, and GitHub), and you can enable more than one for an application.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you wired up "Sign in with GitHub" the hard way before — and would you move that identity layer out of your code and into the WAF?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to protect your sites without paying for a cloud WAF?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/chaitin/SafeLine" rel="noopener noreferrer"&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 href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://demo.waf.chaitin.com:9443/statistics" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>github</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Add Authentication and SSO to Any Web App Without Changing Code</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 17 Aug 2026 16:03:46 +0000</pubDate>
      <link>https://dev.to/lialiago/add-authentication-and-sso-to-any-web-app-without-changing-code-k9g</link>
      <guid>https://dev.to/lialiago/add-authentication-and-sso-to-any-web-app-without-changing-code-k9g</guid>
      <description>&lt;p&gt;Most people treat authentication as an application problem. If an internal dashboard, an admin panel, or a legacy tool doesn't have a login screen, the reflex is to open the codebase and start wiring up an auth library — OAuth clients, session management, user tables, password resets, the whole thing.&lt;/p&gt;

&lt;p&gt;That's a lot of work to do &lt;em&gt;once&lt;/em&gt;, and it's even worse when you have ten different apps that all need it.&lt;/p&gt;

&lt;p&gt;There's a faster way: put the authentication layer in front of your apps instead of inside them. A self-hosted WAF that sits as a reverse proxy in front of your services can handle login for you — before a single request ever reaches your application code.&lt;/p&gt;

&lt;p&gt;Here's how SafeLine does it, and how you can add both password protection and full SSO to any web app without touching its source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do auth at the WAF layer?
&lt;/h2&gt;

&lt;p&gt;A WAF is a reverse proxy: clients connect to the WAF first, the WAF filters and forwards traffic to your origin server. Because every request already flows &lt;em&gt;through&lt;/em&gt; it, the WAF is a natural place to gate access.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No code changes.&lt;/strong&gt; The app behind the WAF doesn't need an auth library, a user table, or any login UI of its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One place to manage.&lt;/strong&gt; Instead of maintaining credentials in ten apps, you manage them in one console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works for anything.&lt;/strong&gt; Legacy apps, static sites, internal tools, APIs — anything the WAF fronts can be protected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SafeLine provides two authentication modes to cover different situations: &lt;strong&gt;Simple Auth&lt;/strong&gt; and &lt;strong&gt;SSO&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Auth vs SSO: two ways to gate access
&lt;/h2&gt;

&lt;p&gt;SafeLine gives you two distinct modes, and the difference comes down to scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple Auth&lt;/strong&gt; is application-specific. A user logs in to access &lt;em&gt;that one&lt;/em&gt; application. If they move to a different app, they authenticate again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSO (Single Sign-On)&lt;/strong&gt; is the opposite. A user logs in once and then accesses &lt;em&gt;multiple&lt;/em&gt; related applications without repeated logins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of Simple Auth as "lock this one door," and SSO as "issue one badge that opens every door you're allowed into."&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Auth: password-protect a single app
&lt;/h2&gt;

&lt;p&gt;With Simple Auth turned on for an application, visitors must enter valid credentials before they can reach the app. Anyone who doesn't hold the credentials is blocked.&lt;/p&gt;

&lt;p&gt;When you configure Simple Auth for an application, you pick a &lt;strong&gt;login method&lt;/strong&gt; and set an &lt;strong&gt;Auth Callback URL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The login method is how users prove who they are. SafeLine supports four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account + Password&lt;/strong&gt; — a basic username/password login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; — sign in with a GitHub account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OIDC&lt;/strong&gt; — sign in through an OpenID Connect identity provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LDAP&lt;/strong&gt; — sign in with an existing enterprise directory account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Auth Callback URL&lt;/strong&gt; is what lets you pass the authenticated user's identity into your app, which I'll cover in detail below.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSO: one login, every app
&lt;/h2&gt;

&lt;p&gt;SSO is where this gets genuinely useful for teams. Once unified authentication is enabled, your users get a single sign-in entry point — an SSO center — and from there they can reach every application that has joined.&lt;/p&gt;

&lt;p&gt;The docs describe three business scenarios where this fits naturally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise application portal.&lt;/strong&gt; Internal systems like OA, HR, and CRM each have their own auth today. With SSO, employees log in once and reach all of them, while IT centralizes access policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modernizing legacy apps.&lt;/strong&gt; Older apps often have no built-in auth, or a dated mechanism. SSO adds a modern authentication layer on top &lt;em&gt;without modifying application code&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero trust.&lt;/strong&gt; SSO is a core piece of a zero-trust architecture, giving every application access a consistent identity-verification and authorization path.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enabling SSO
&lt;/h3&gt;

&lt;p&gt;You enable SSO from the &lt;strong&gt;Auth-Settings&lt;/strong&gt; page under the SSO tab. The configuration is a handful of fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The domain users visit to reach the unified authentication center (e.g. &lt;code&gt;home.in.example.cn&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Port&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An available port for the center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optional, for HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Login Methods&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The identity methods available (multiple can be enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Force Enable TOTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When on, &lt;em&gt;every&lt;/em&gt; SSO user must use TOTP for two-factor authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once enabled, users in the user list can access the SSO center by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding apps to SSO
&lt;/h3&gt;

&lt;p&gt;To bring an application into SSO, go to &lt;strong&gt;Applications&lt;/strong&gt;, click the app's &lt;strong&gt;AUTH&lt;/strong&gt; button, and switch it over. Then you configure two fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Redirect URL&lt;/strong&gt; — where the user is sent when they click the app in the SSO center.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth Callback URL&lt;/strong&gt; — how authentication info is passed to the app server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, when a user visits an application for the first time, they authenticate once. From then on, that one authentication covers every authorized application that has joined SSO.&lt;/p&gt;

&lt;p&gt;A few details worth knowing from the SSO docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is &lt;strong&gt;no limit&lt;/strong&gt; on how many applications can join SSO.&lt;/li&gt;
&lt;li&gt;You can edit an application's name and logo in the SSO center.&lt;/li&gt;
&lt;li&gt;Users can only access applications they have permission for — joining SSO doesn't grant access to everything by default.&lt;/li&gt;
&lt;li&gt;Logging out of the SSO center is &lt;strong&gt;not&lt;/strong&gt; SLO (single logout) — that's not currently supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The four login methods
&lt;/h2&gt;

&lt;p&gt;Whichever mode you use, the actual identity check is done by one of four methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account + Password
&lt;/h3&gt;

&lt;p&gt;The simplest option. You configure a username and password, and visitors enter it to get through. It's a good fit for a single admin or a small set of trusted users.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;SafeLine can let users sign in with their GitHub identity. You create a GitHub OAuth application to get a &lt;code&gt;ClientID&lt;/code&gt; and &lt;code&gt;ClientSecret&lt;/code&gt;, then fill those into SafeLine's third-party login configuration. The callback URL for GitHub follows the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-app.com/.safeline/auth/api/callback/github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also an "Auto Merge Account" option to match incoming GitHub logins to existing accounts by email.&lt;/p&gt;

&lt;h3&gt;
  
  
  OIDC
&lt;/h3&gt;

&lt;p&gt;For organizations that already run an identity provider, SafeLine speaks OpenID Connect. The docs call out Keycloak, Auth0, Okta, and Azure AD as supported providers.&lt;/p&gt;

&lt;p&gt;Configuring it means pointing SafeLine at your provider's discovery URL (for Keycloak, something like &lt;code&gt;https://{keycloak-host}/realms/{realm-name}&lt;/code&gt;) plus a Client ID and Client Secret. This is the method to reach for when you want your apps to trust the identity system you already have.&lt;/p&gt;

&lt;h3&gt;
  
  
  LDAP
&lt;/h3&gt;

&lt;p&gt;For enterprise directories, SafeLine supports LDAP — including Microsoft Active Directory, OpenLDAP, and Apache Directory. Users log in with their existing enterprise accounts.&lt;/p&gt;

&lt;p&gt;Configuration is the standard set of LDAP fields: a server URL (&lt;code&gt;ldap://host:389&lt;/code&gt; or &lt;code&gt;ldaps://host:636&lt;/code&gt; for SSL), a Bind DN, a Bind password, a user base DN, and a query condition. The query condition uses &lt;code&gt;%s&lt;/code&gt; as a placeholder for the username, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(&amp;amp;(objectClass=person)(uid=%s))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the one to use when you want a self-hosted gateway in front of an on-premise directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing the authenticated user into your app
&lt;/h2&gt;

&lt;p&gt;Blocking unauthenticated traffic is only half the story. Often your app needs to &lt;em&gt;know who&lt;/em&gt; just logged in. SafeLine handles this with a callback flow.&lt;/p&gt;

&lt;p&gt;After a user authenticates, SafeLine redirects to your configured &lt;strong&gt;Auth Callback URL&lt;/strong&gt;, appending a short-lived &lt;code&gt;code&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://example.com/application?code=123456&amp;amp;redirect_uri=original_user_access_address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application then exchanges that &lt;code&gt;code&lt;/code&gt; for the user's identity by calling SafeLine's user-info endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET http://safeline-console.com/.safeline/auth/api/user?code=123456
X-SLCE-API-TOKEN: safeline-api-token
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you the user's &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;username&lt;/code&gt;. Two things to note: the &lt;code&gt;code&lt;/code&gt; can only be used &lt;strong&gt;once&lt;/strong&gt;, and you'll find the API token in the console under &lt;strong&gt;Settings → Management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After your app obtains the user info, it should cache the login state to decide whether the user is logged in for subsequent requests, then redirect to the &lt;code&gt;redirect_uri&lt;/code&gt;. If retrieving the user info fails, your app should delete cookies with the &lt;code&gt;sl_auth_session_&lt;/code&gt; prefix and prompt the user to authenticate again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approvals and conditional auth
&lt;/h2&gt;

&lt;p&gt;Two more knobs round out the picture, both under the app's AUTH advanced configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approval configuration.&lt;/strong&gt; You can require that first-time users trigger an approval request after authenticating, which an admin must approve before the user gets in. Or you can let users access directly after authentication. The former is handy for sensitive internal tools where you want a human in the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional authentication.&lt;/strong&gt; You can enable authentication (or skip it) only when specific conditions are met — for example, requiring auth for certain paths while letting other traffic pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Authentication doesn't have to be a per-app engineering project. Because a self-hosted WAF already sits in front of your traffic, it's the natural place to gate access — and SafeLine turns that into a real feature set: per-app password protection, unified SSO across your apps, and four identity methods (password, GitHub, OIDC, LDAP) to choose from.&lt;/p&gt;

&lt;p&gt;The result is that an internal dashboard, a legacy tool, or a static site can all get a proper login screen — and SSO — without a single line of application code changing.&lt;/p&gt;

&lt;p&gt;If you want to try it yourself, the setup starts with a single command. The full authentication documentation is at &lt;a href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;docs.waf.chaitin.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does adding auth at the WAF layer slow down my app?
&lt;/h3&gt;

&lt;p&gt;The check happens at the reverse-proxy layer before traffic reaches your origin, so your application itself isn't doing any extra work. The main cost is the one-time configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use SSO for both internal and public apps?
&lt;/h3&gt;

&lt;p&gt;Yes. The mode is per-application, so you can put internal tools behind SSO while using Simple Auth or no auth on public apps, all from the same console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which identity provider should I pick?
&lt;/h3&gt;

&lt;p&gt;If you already run Keycloak, Auth0, Okta, or Azure AD, OIDC is the natural fit. If you have an enterprise directory, use LDAP. GitHub is convenient for developer-facing tools, and Account + Password is fine for a small number of trusted users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I have to modify my app to know who logged in?
&lt;/h3&gt;

&lt;p&gt;Only if your app needs the user's identity. In that case you implement the callback flow (exchange the &lt;code&gt;code&lt;/code&gt; for user info). If you just need to gate access, no code changes are required at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does SafeLine's SSO support single logout?
&lt;/h3&gt;

&lt;p&gt;Not currently. Logging out of the SSO center is not SLO.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which of the four login methods — password, GitHub, OIDC, or LDAP — fits your current stack?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to protect your sites without paying for a cloud WAF?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/chaitin/SafeLine" rel="noopener noreferrer"&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 href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://demo.waf.chaitin.com:9443/statistics" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>How to Protect Multiple Websites with One WAF Instance</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Wed, 12 Aug 2026 02:58:53 +0000</pubDate>
      <link>https://dev.to/lialiago/how-to-protect-multiple-websites-with-one-waf-instance-5d60</link>
      <guid>https://dev.to/lialiago/how-to-protect-multiple-websites-with-one-waf-instance-5d60</guid>
      <description>&lt;h3&gt;
  
  
  The Multi-Site Use Case
&lt;/h3&gt;

&lt;p&gt;You run a VPS with 3 sites: a WordPress blog on &lt;code&gt;blog.example.com&lt;/code&gt;, a SaaS app on &lt;code&gt;app.example.com&lt;/code&gt;, and a static landing page on &lt;code&gt;example.com&lt;/code&gt;. You want one WAF to protect all three — no per-site overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Multiple Sites to SafeLine
&lt;/h3&gt;

&lt;p&gt;In the SafeLine dashboard, Sites -&amp;gt; Add Site -&amp;gt; repeat for each domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Site&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;: &lt;span class="n"&gt;blog&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt; -&amp;gt; &lt;span class="n"&gt;backend&lt;/span&gt;: &lt;span class="n"&gt;wordpress&lt;/span&gt;:&lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="n"&gt;Site&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;: &lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;  -&amp;gt; &lt;span class="n"&gt;backend&lt;/span&gt;: &lt;span class="n"&gt;app&lt;/span&gt;:&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;span class="n"&gt;Site&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;: &lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;      -&amp;gt; &lt;span class="n"&gt;backend&lt;/span&gt;: &lt;span class="n"&gt;nginx&lt;/span&gt;:&lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three sites route through the same SafeLine instance. The semantic engine inspects traffic for all of them independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-Site Rules Without Per-Site WAFs
&lt;/h3&gt;

&lt;p&gt;Each site gets its own protection profile:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Rate Limiting&lt;/th&gt;
&lt;th&gt;Geo-Blocking&lt;/th&gt;
&lt;th&gt;Bot Protection&lt;/th&gt;
&lt;th&gt;Custom Rules&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5/min on /wp-login&lt;/td&gt;
&lt;td&gt;Block RU, CN, VN&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;td&gt;Block /xmlrpc.php&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SaaS App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100/min per API key&lt;/td&gt;
&lt;td&gt;None (global product)&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;td&gt;Block mass assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Landing Page&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Global 300/min&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is all configured in one dashboard — no separate WAF installations, no per-site Docker containers, no added memory overhead beyond the single SafeLine deployment (~700 MB total for all sites).&lt;/p&gt;

&lt;h3&gt;
  
  
  SSL Certificate Management
&lt;/h3&gt;

&lt;p&gt;SafeLine handles SSL for all three sites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Site 1: Let's Encrypt auto-renew for blog.example.com
Site 2: Upload custom wildcard cert for *.example.com
Site 3: Also covered by the wildcard cert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or terminate SSL at your existing Nginx/Caddy and have SafeLine work in plain HTTP behind it — your choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Usage at Scale
&lt;/h3&gt;

&lt;p&gt;SafeLine's resource usage scales efficiently with multiple sites:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sites Protected&lt;/th&gt;
&lt;th&gt;RAM Usage&lt;/th&gt;
&lt;th&gt;CPU at 100 req/s total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;700 MB&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;730 MB&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;780 MB&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fixed overhead is PostgreSQL + management service (~500 MB). Each additional site adds minimal memory — just a few MB for the routing table. The Tengine container handles all the inspection work; adding more sites increases CPU linearly but memory barely budges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Isolation
&lt;/h3&gt;

&lt;p&gt;Sites are isolated at the routing layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A security event on &lt;code&gt;blog.example.com&lt;/code&gt; doesn't affect &lt;code&gt;app.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rate limits apply per-site, not globally (5/min on blog login doesn't affect SaaS app limits)&lt;/li&gt;
&lt;li&gt;Attack logs are tagged by site — you can filter by domain in the dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Save Money: Replace Per-App Security Tools
&lt;/h3&gt;

&lt;p&gt;Before SafeLine, you might have been using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A WordPress security plugin ($0-100/year per site)&lt;/li&gt;
&lt;li&gt;Cloudflare Pro for the SaaS app ($20/month)&lt;/li&gt;
&lt;li&gt;fail2ban + manual Nginx rules for the landing page (free but high maintenance)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After: one SafeLine instance ($0 for Community, $10/month for Lite) replaces all of it. The WordPress plugin goes away (WAF handles the same attacks). Cloudflare goes to free tier (WAF handles security, Cloudflare just does DNS). fail2ban gets simpler (WAF catches HTTP attacks, fail2ban only needs SSH).&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Can I mix HTTP and HTTPS sites on the same WAF?
&lt;/h3&gt;

&lt;p&gt;Yes. Each site has its own SSL config. Blog can be HTTPS with Let's Encrypt while the landing page is HTTP-only. SafeLine handles the routing for both.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if one site gets a huge traffic spike?
&lt;/h3&gt;

&lt;p&gt;SafeLine Community Edition maxes at 800 QPS total. If one site spikes and you hit the cap, all sites are affected. Upgrade to the Lite plan ($10/month, no QPS cap) if you run high-traffic sites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I give separate dashboard access to different clients?
&lt;/h3&gt;

&lt;p&gt;Not in the Community Edition — one admin account. The Pro plan supports role-based access. For agency use, consider the upgrade.&lt;/p&gt;




&lt;p&gt;How many sites are running on your VPS right now without any WAF in front?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to protect your sites without paying for a cloud WAF?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/chaitin/SafeLine" rel="noopener noreferrer"&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 href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://demo.waf.chaitin.com:9443/statistics" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Block SQL Injection Attacks with a Free WAF</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Tue, 11 Aug 2026 07:01:02 +0000</pubDate>
      <link>https://dev.to/lialiago/how-to-block-sql-injection-attacks-with-a-free-waf-2dda</link>
      <guid>https://dev.to/lialiago/how-to-block-sql-injection-attacks-with-a-free-waf-2dda</guid>
      <description>&lt;h3&gt;
  
  
  SQL Injection Is Still the #1 Threat in 2026
&lt;/h3&gt;

&lt;p&gt;OWASP Top 10 hasn't changed. SQL injection remains the most dangerous web vulnerability — and the most common. In 2025, 23% of all reported web vulnerabilities were injection flaws. A WAF can block 99% of them at the proxy level before they reach your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  What SQL Injection Looks Like
&lt;/h3&gt;

&lt;p&gt;Attackers probe for SQL injection with payloads like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="s1"&gt;' OR '&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="s1"&gt;'; DROP TABLE users; --
'&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="s1"&gt;' OR 1=1 LIMIT 1 --
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These all follow a pattern: they try to escape the SQL query context and inject their own commands. A semantic WAF can detect this pattern structure, not just specific payload strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Application-Level Fixes Aren't Enough
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prepared statements (the gold standard)&lt;/strong&gt; prevent SQL injection. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not every query in your codebase uses them&lt;/li&gt;
&lt;li&gt;Third-party libraries might have raw queries&lt;/li&gt;
&lt;li&gt;Legacy code exists and nobody wants to touch it&lt;/li&gt;
&lt;li&gt;ORMs have edge cases (raw queries, dynamic joins)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A WAF is defense in depth. Even if your application is perfect, the WAF catches what you missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up SQL Injection Protection in SafeLine
&lt;/h3&gt;

&lt;p&gt;SafeLine's semantic engine detects SQL injection without regex rules. It parses the SQL structure of incoming payloads and tests whether they could modify query behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No custom rules needed.&lt;/strong&gt; The semantic engine ships with SQL injection detection built in. To enable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Dashboard -&amp;gt; Settings -&amp;gt; Detection Profile
2. SQL Injection: Block (not just Detect)
3. Confidence threshold: 80 (default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every SQL injection attempt hitting your server is now blocked at the WAF layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works: Semantic vs Pattern Matching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pattern matching (old WAFs):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UNION&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;SELECT&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;DROP&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="nx"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blocks&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;
         &lt;span class="nx"&gt;Misses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;U%4EION SEL%45CT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt; &lt;span class="nx"&gt;bypass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Semantic analysis (SafeLine):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Parse the payload as SQL structure
2. Identify: this modifies the query's logic tree
3. Classify: SQL injection with 98% confidence
4. Block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test It Yourself
&lt;/h3&gt;

&lt;p&gt;After setting up SafeLine, test with a safe probe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This should be blocked (returns 403)&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://yourdomain.com/?id=1' OR '1'='1"&lt;/span&gt;

&lt;span class="c"&gt;# Check the Attack Logs in SafeLine dashboard&lt;/span&gt;
&lt;span class="c"&gt;# You should see: SQL Injection, Blocked, Score: 98/100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  False Positives — What Actually Gets Through
&lt;/h3&gt;

&lt;p&gt;SafeLine's semantic engine has a 0.07% false positive rate on SQL injection detection. What can trigger it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legal documents with SQL snippets&lt;/strong&gt;: If your site publishes database tutorials or SQL reference docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form data with SQL keywords&lt;/strong&gt;: A field labeled "SQL query" submitted by a user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these edge cases, add path whitelists for specific URLs in the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Does this replace prepared statements?
&lt;/h3&gt;

&lt;p&gt;No. Prepared statements are still the right way to write queries. The WAF is defense in depth — it catches the SQL injection that gets past your application code. Use both.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about NoSQL injection?
&lt;/h3&gt;

&lt;p&gt;SafeLine's semantic engine also detects NoSQL injection patterns (MongoDB operators like &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$ne&lt;/code&gt;, &lt;code&gt;$where&lt;/code&gt;). The same single-pass parsing approach applies to JSON-encoded NoSQL queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test SQL injection on my own site?
&lt;/h3&gt;

&lt;p&gt;Yes — but only through the WAF. Don't test directly against your database. Use curl with safe payloads and watch Attack Logs to confirm they're blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about blind SQL injection?
&lt;/h3&gt;

&lt;p&gt;Blind SQL injection (&lt;code&gt;AND SLEEP(5)&lt;/code&gt;, &lt;code&gt;AND 1=IF(2&amp;gt;1,BENCHMARK(5000000,MD5('A')),0)&lt;/code&gt;) uses timing-based extraction instead of visible output. SafeLine detects both blind and in-band SQL injection with the same semantic parsing.&lt;/p&gt;




&lt;p&gt;When was the last time you tested your app for SQL injection?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#security&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#cybersecurity&lt;/code&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Docker Compose WAF: Add SafeLine to Any Existing Stack in 3 Steps</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Tue, 11 Aug 2026 07:00:53 +0000</pubDate>
      <link>https://dev.to/lialiago/docker-compose-waf-add-safeline-to-any-existing-stack-in-3-steps-np5</link>
      <guid>https://dev.to/lialiago/docker-compose-waf-add-safeline-to-any-existing-stack-in-3-steps-np5</guid>
      <description>&lt;h3&gt;
  
  
  The Situation
&lt;/h3&gt;

&lt;p&gt;You already have a Docker Compose stack running: Nginx + your app + PostgreSQL + Redis. It works. You don't want to rebuild it. But you want WAF protection in front.&lt;/p&gt;

&lt;p&gt;Here's how to add SafeLine to an existing Docker Compose project without disrupting anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Shared Network
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (add to your existing file)&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect your existing services to this network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webnet&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webnet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install SafeLine Separately
&lt;/h3&gt;

&lt;p&gt;SafeLine uses its own Compose file. The installer creates it automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLk&lt;/span&gt; https://waf.chaitin.com/release/latest/manager.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--en&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated &lt;code&gt;docker-compose.yml&lt;/code&gt; (for SafeLine) will be something like &lt;code&gt;/opt/safeline/docker-compose.yml&lt;/code&gt;. Leave it alone — SafeLine manages this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Connect SafeLine to Your Network
&lt;/h3&gt;

&lt;p&gt;Edit SafeLine's Compose file (&lt;code&gt;/opt/safeline/docker-compose.yml&lt;/code&gt;) and add your existing network as external:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;safeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
  &lt;span class="na"&gt;webnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Your existing network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect SafeLine's reverse proxy to both networks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;safeline-tengine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;safeline&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webnet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Point Traffic Through SafeLine
&lt;/h3&gt;

&lt;p&gt;Update your Nginx config to route traffic through SafeLine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (direct to app)&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://app:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# After (through SafeLine)&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://safeline-tengine:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in SafeLine's dashboard, point the site backend to your app container: &lt;code&gt;app:3000&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Restart
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start all your services&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# SafeLine should already be running from the installer&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/safeline/docker-compose.yml ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traffic flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → Nginx:80 → SafeLine (inspection) → app:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Beauty of This Setup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Your app doesn't change.&lt;/strong&gt; SafeLine sits between Nginx and your app as a middleware proxy. No code changes. No config changes to your app containers. Just 3 lines in your Nginx config and one network connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SafeLine updates independently.&lt;/strong&gt; When you update your app stack, SafeLine containers aren't touched. When SafeLine releases a new version, your app stack isn't touched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can test before switching.&lt;/strong&gt; Set up SafeLine in detection-only mode first. Watch Attack Logs for a few days. Confirm zero false positives. Then switch to block mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration from Existing Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;If you currently use Nginx Proxy Manager, Traefik, or Caddy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Traefik example&lt;/span&gt;
&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.app.middlewares=safeline"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.safeline.forwardauth.address=http://safeline-tengine:80"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Nginx Proxy Manager, add a custom location that forwards to SafeLine before reaching your app. The principle is the same: NPM -&amp;gt; SafeLine -&amp;gt; your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Does this work with Kubernetes?
&lt;/h3&gt;

&lt;p&gt;The pattern is the same but implemented differently. Use a DaemonSet for SafeLine or deploy it as a sidecar. The concept of "insert WAF between ingress and app" applies regardless of orchestrator.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if SafeLine goes down?
&lt;/h3&gt;

&lt;p&gt;Your app still runs, but traffic won't reach it — SafeLine is in the critical path. Mitigate with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/safeline/docker-compose.yml restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a health check to your monitoring tool. If you need high availability, run SafeLine on a separate node with a load balancer in front.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run SafeLine as a container in my existing compose file?
&lt;/h3&gt;

&lt;p&gt;You can, but the installer-generated Compose file handles SSL certs, PostgreSQL, and the management service. It's safer to keep SafeLine as a separate compose project connected by shared networks.&lt;/p&gt;




&lt;p&gt;What's your current Docker Compose stack looking like?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#docker&lt;/code&gt; &lt;code&gt;#security&lt;/code&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Self-Hosted WAF on a $5 VPS: My Budget Security Setup</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:37:49 +0000</pubDate>
      <link>https://dev.to/lialiago/self-hosted-waf-on-a-5-vps-my-budget-security-setup-1pja</link>
      <guid>https://dev.to/lialiago/self-hosted-waf-on-a-5-vps-my-budget-security-setup-1pja</guid>
      <description>&lt;h3&gt;
  
  
  The $5 Challenge
&lt;/h3&gt;

&lt;p&gt;Can you run a production-grade WAF on the cheapest VPS available? I tested it on a $5/month VPS (1 vCPU, 1 GB RAM, 25 GB SSD) from a major provider. The goal: protect a Node.js blog and a static site without exceeding the budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│ $5 VPS (1 vCPU, 1 GB RAM)           │
│                                     │
│  ┌─────────┐  ┌──────────┐          │
│  │ SafeLine │  │ Node.js  │          │
│  │ WAF      │  │ Blog App │          │
│  │ ~500 MB  │  │ ~300 MB  │          │
│  └────┬─────┘  └────┬─────┘          │
│       │              │               │
│  ┌────┴──────────────┴──┐           │
│  │   Nginx (reverse)     │           │
│  │   ~50 MB              │           │
│  └───────────────────────┘           │
│                                     │
│  Total RAM used: ~850 MB            │
│  Free: ~150 MB                      │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Worked
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. SafeLine on 1GB is tight but functional&lt;/strong&gt;&lt;br&gt;
The WAF's Docker containers used ~500 MB at idle (slightly less than the usual 700 MB — the 1GB constraint forced more aggressive memory management). Under moderate load (100 req/s), it stayed at ~550 MB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Node.js blog ran fine with 300 MB&lt;/strong&gt;&lt;br&gt;
A simple Express app with SQLite database. Generated pages in ~15ms. The WAF added 1ms. Nobody noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The sweet surprise: swap file saved us&lt;/strong&gt;&lt;br&gt;
I added a 1 GB swap file on disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 1G /swapfile
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /swapfile
mkswap /swapfile
swapon /swapfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When under load, the kernel swapped PostgreSQL's idle memory pages. SafeLine stayed responsive. The swap file is essential on a 1 GB box.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Didn't Work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. WordPress on the same box&lt;/strong&gt;&lt;br&gt;
WordPress + MySQL + PHP-FPM on the same 1 GB VPS with SafeLine hit the OOM killer within 2 hours. Separated WordPress to its own $5 VPS and it worked fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Concurrent deployments&lt;/strong&gt;&lt;br&gt;
SafeLine + &lt;code&gt;docker compose up -d&lt;/code&gt; + &lt;code&gt;npm run build&lt;/code&gt; happening simultaneously triggered OOM. Staggered deployments: build first, then start WAF containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Heavy PostgreSQL logging&lt;/strong&gt;&lt;br&gt;
Default SafeLine PostgreSQL settings wrote attack logs aggressively. Tuned &lt;code&gt;postgresql.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;shared_buffers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;64MB&lt;/span&gt;
&lt;span class="py"&gt;effective_cache_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256MB&lt;/span&gt;
&lt;span class="py"&gt;maintenance_work_mem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;32MB&lt;/span&gt;
&lt;span class="py"&gt;wal_buffers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;8MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These PostgreSQL tweaks saved 100 MB of RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Results (1GB VPS, 100 concurrent users)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Without WAF&lt;/th&gt;
&lt;th&gt;With SafeLine&lt;/th&gt;
&lt;th&gt;Acceptable?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg response time&lt;/td&gt;
&lt;td&gt;15ms&lt;/td&gt;
&lt;td&gt;16ms&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests/sec&lt;/td&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;850&lt;/td&gt;
&lt;td&gt;✅ (-29%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM baseline&lt;/td&gt;
&lt;td&gt;350 MB&lt;/td&gt;
&lt;td&gt;850 MB&lt;/td&gt;
&lt;td&gt;⚠️ tight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RAM under load&lt;/td&gt;
&lt;td&gt;450 MB&lt;/td&gt;
&lt;td&gt;1,020 MB&lt;/td&gt;
&lt;td&gt;⚠️ uses swap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The bottleneck was RAM, not CPU or WAF throughput.&lt;/strong&gt; On a 2 GB VPS, throughput is 7,600 req/s. On 1 GB, it drops because the system spends cycles managing memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is $5 Enough?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;$5 VPS Enough?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static site + WAF&lt;/td&gt;
&lt;td&gt;✅ Yes, plenty of room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small Node/Python app + WAF&lt;/td&gt;
&lt;td&gt;✅ Yes, with swap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WordPress + WAF&lt;/td&gt;
&lt;td&gt;❌ No, need 2 GB minimum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple apps + WAF&lt;/td&gt;
&lt;td&gt;❌ No, get 2-4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API with heavy DB queries&lt;/td&gt;
&lt;td&gt;❌ No, CPU + RAM starved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Why not just use Cloudflare's free plan?
&lt;/h3&gt;

&lt;p&gt;Cloudflare free terminates TLS at their edge — they can see your traffic in plaintext. If you handle sensitive data, a self-hosted WAF means the traffic is decrypted and inspected on your own server. No third party sees it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run SafeLine on a Raspberry Pi instead of a VPS?
&lt;/h3&gt;

&lt;p&gt;Yes. A Pi 4 with 4 GB RAM is actually more comfortable than a $5 VPS. The ARM Docker images work. Electricity costs about $5/year. Perfect for homelab use.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my site gets popular and outgrows 1 GB?
&lt;/h3&gt;

&lt;p&gt;Upgrade to a $10/month 2 GB VPS. The WAF itself doesn't need more — your app growing is what drives the upgrade. SafeLine scales linearly: add more CPU and it handles more QPS.&lt;/p&gt;




&lt;p&gt;What's running on your $5 VPS right now?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#security&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>WAF Logs Explained: How to Read and Act on Attack Data</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:37:39 +0000</pubDate>
      <link>https://dev.to/lialiago/waf-logs-explained-how-to-read-and-act-on-attack-data-43lj</link>
      <guid>https://dev.to/lialiago/waf-logs-explained-how-to-read-and-act-on-attack-data-43lj</guid>
      <description>&lt;h3&gt;
  
  
  The Dashboard Isn't Just Numbers
&lt;/h3&gt;

&lt;p&gt;Your WAF blocks attacks. The Attack Logs tell you what those attacks were, where they came from, and whether any got through. Reading these logs turns random blocks into actionable intelligence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of an Attack Log Entry
&lt;/h3&gt;

&lt;p&gt;When SafeLine blocks an attack, each log entry shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time: 2026-08-06 03:12:45 UTC
Source IP: 185.220.101.34 (Russia)
Target: example.com/login
Attack type: SQL Injection
Payload: admin' OR '1'='1' --
Action: Blocked
Rule: Semantic analysis (score: 98/100)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything you need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; it happened (3 AM = automated, not targeted)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; from (Russia = likely scanner, not a customer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; they tried (SQL injection on login = credential bypass)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence&lt;/strong&gt; (98/100 = near-certain attack)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Read Logs for Patterns (Not Just Events)
&lt;/h3&gt;

&lt;p&gt;One blocked SQL injection is noise. Ten blocked SQL injections from the same IP range, all targeting &lt;code&gt;/api/graphql&lt;/code&gt;, at 3 AM — that's a coordinated attack. Here's what to look for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern 1: Geographic concentration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20+ attacks from Russian IPs -&amp;gt; all targeting /wp-login.php
-&amp;gt; Action: Geo-block Russia if you don't serve that market
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pattern 2: Endpoint targeting&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50+ attacks on /api/graphql over 2 hours
-&amp;gt; Action: Add GraphQL-specific rate limiting, review query depth limits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pattern 3: Payload evolution&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 1: Basic SQL injection (' OR 1=1)
Day 2: Blind SQL injection (sleep functions)
Day 3: UNION-based injection
-&amp;gt; Action: Attacker is iterating. Block the IP range aggressively.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pattern 4: Quiet Monday, loud Thursday&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monday: 200 blocked attacks
Thursday: 1,200 blocked attacks
-&amp;gt; This is normal. Attack volume spikes mid-week. Don't freak out.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What to Ignore
&lt;/h3&gt;

&lt;p&gt;Not every log entry matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single SQL injection probes&lt;/strong&gt; — scanners try every URL. It'll never work on updated software.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/.env&lt;/code&gt; requests&lt;/strong&gt; — automated scanners looking for Laravel config leaks. If you're not on Laravel, ignore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port scans&lt;/strong&gt; — your WAF only sees HTTP. Port scans hitting closed ports are not your problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress path scans on non-WordPress sites&lt;/strong&gt; — harmless noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to Act On Immediately
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple failed logins from the same IP&lt;/strong&gt; — bump up rate limiting on auth endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Successful logins from unusual locations&lt;/strong&gt; — this gets through the WAF but should trigger application-level alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any attack with confidence score &amp;gt; 95&lt;/strong&gt; — near-certain actual attack, worth reviewing what they targeted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attacks on custom endpoints&lt;/strong&gt; — someone mapped your API. They know your stack. Take this seriously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data exfiltration patterns&lt;/strong&gt; — large outbound responses after suspicious POST requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using Logs to Tune Your WAF Rules
&lt;/h3&gt;

&lt;p&gt;After 2 weeks of log analysis, here's what I adjusted:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Adjustment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;80% of attacks from 5 countries&lt;/td&gt;
&lt;td&gt;Enabled geo-blocking for those countries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login endpoint hit 300x/day&lt;/td&gt;
&lt;td&gt;Added 5/min rate limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/api/search&lt;/code&gt; scraped by script&lt;/td&gt;
&lt;td&gt;JS challenge for high-frequency search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero attacks on &lt;code&gt;/health&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Added whitelist (no inspection needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 false positives on webhook receiver&lt;/td&gt;
&lt;td&gt;Whitelisted the webhook source IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  How long should I keep attack logs?
&lt;/h3&gt;

&lt;p&gt;At least 30 days. Attack patterns emerge over weeks, not hours. SafeLine uses PostgreSQL for log storage — set retention to 90 days unless you're tight on disk space. Attackers sometimes come back after 2-3 weeks with different techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between "Blocked" and "Detected"?
&lt;/h3&gt;

&lt;p&gt;"Blocked" means the request was stopped at the WAF. "Detected" means the WAF flagged it but you're in monitoring mode. Switch to blocking mode once you've confirmed no false positives for your traffic patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I report attacks to anyone?
&lt;/h3&gt;

&lt;p&gt;For most small sites: no. Law enforcement won't act on individual attack logs. If you see a coordinated, persistent attack from a specific hosting provider, you can send an abuse report to their network operations team. That occasionally works.&lt;/p&gt;




&lt;p&gt;What's the most common attack showing up in your server logs right now?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#security&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#monitoring&lt;/code&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Bot Protection 101: Block Scrapers and Bad Bots Without Affecting Google</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:19:58 +0000</pubDate>
      <link>https://dev.to/lialiago/bot-protection-101-block-scrapers-and-bad-bots-without-affecting-google-2bm9</link>
      <guid>https://dev.to/lialiago/bot-protection-101-block-scrapers-and-bad-bots-without-affecting-google-2bm9</guid>
      <description>&lt;h3&gt;
  
  
  Good Bots vs Bad Bots
&lt;/h3&gt;

&lt;p&gt;Not all bots are created equal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bots You Want&lt;/th&gt;
&lt;th&gt;Bots You Dont Want&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Googlebot&lt;/strong&gt; — indexes your site for search&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Scrapers&lt;/strong&gt; — steal your content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Bingbot&lt;/strong&gt; — same as Google, different engine&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Credential stuffers&lt;/strong&gt; — try leaked passwords&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Ahrefs / Semrush&lt;/strong&gt; — SEO analysis (debatable)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Vulnerability scanners&lt;/strong&gt; — probe for weak points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Uptime monitors&lt;/strong&gt; — Pingdom, Better Uptime&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;AI training crawlers&lt;/strong&gt; — GPTBot, CCBot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Feed readers&lt;/strong&gt; — RSS readers, Feedly&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Price scrapers&lt;/strong&gt; — competitor intelligence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal: block bad bots while letting good ones through. A WAF with bot detection makes this automatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  How SafeLines Bot Protection Works
&lt;/h3&gt;

&lt;p&gt;SafeLine uses three layers to identify bots:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Static analysis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User-Agent pattern matching against known bots&lt;/li&gt;
&lt;li&gt;IP reputation database (known scanner IPs)&lt;/li&gt;
&lt;li&gt;Request header consistency checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Behavioral analysis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request frequency and timing patterns&lt;/li&gt;
&lt;li&gt;Page navigation sequences&lt;/li&gt;
&lt;li&gt;Resource loading behavior (does it load CSS/JS/images?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Active challenge&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript challenge (execute JS, prove youre a browser)&lt;/li&gt;
&lt;li&gt;CAPTCHA (image/text challenge)&lt;/li&gt;
&lt;li&gt;Cookie validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up Bot Protection
&lt;/h3&gt;

&lt;p&gt;In SafeLine Dashboard -&amp;gt; Bot Management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Enable Bot Detection: ON
2. Action for suspicious bots: JavaScript Challenge
3. Action for confirmed bad bots: Block
4. Whitelist: Googlebot, Bingbot, Pingdom, Stripe webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thats four settings and youre protected. The WAF handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Caught on My Server
&lt;/h3&gt;

&lt;p&gt;After enabling bot protection for 7 days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bot Type&lt;/th&gt;
&lt;th&gt;Detected&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credential stuffing attempts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,847&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Content scrapers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,203&lt;/td&gt;
&lt;td&gt;JS challenged (all failed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vulnerability scanners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;892&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI crawlers (GPTBot, CCBot)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;456&lt;/td&gt;
&lt;td&gt;JS challenged (all failed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SEO crawlers (Ahrefs, Semrush)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;234&lt;/td&gt;
&lt;td&gt;Allowed (didnt trigger detection)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Googlebot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,456&lt;/td&gt;
&lt;td&gt;Whitelisted (never challenged)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero legitimate users reported CAPTCHA fatigue. Google continued indexing normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Will bot protection break my analytics?
&lt;/h3&gt;

&lt;p&gt;No. Google Analytics, Plausible, and similar tools use JavaScript execution. Legitimate analytics are unaffected.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about API clients (Postman, curl, SDKs)?
&lt;/h3&gt;

&lt;p&gt;API endpoints with proper authentication should be added to the bot protection whitelist. Bots that authenticate with valid tokens are treated differently from anonymous scrapers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it affect page load speed?
&lt;/h3&gt;

&lt;p&gt;The JS challenge adds about 200ms on the first request for suspicious visitors. Legitimate users see zero delay.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try SafeLine Community Edition — free, self-hosted, and takes 5 minutes to deploy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLk&lt;/span&gt; https://waf.chaitin.com/release/latest/manager.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--en&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dashboard: &lt;code&gt;https://&amp;lt;your-server-ip&amp;gt;:9443&lt;/code&gt; | &lt;a href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How much of your current traffic do you think is bots?&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Geo-Blocking: Block Malicious Traffic from Specific Countries (2-Minute Setup)</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:19:03 +0000</pubDate>
      <link>https://dev.to/lialiago/geo-blocking-block-malicious-traffic-from-specific-countries-2-minute-setup-h3i</link>
      <guid>https://dev.to/lialiago/geo-blocking-block-malicious-traffic-from-specific-countries-2-minute-setup-h3i</guid>
      <description>&lt;h3&gt;
  
  
  Why Geo-Block?
&lt;/h3&gt;

&lt;p&gt;Not every country needs to reach your server. If you run a local business in Brazil, you don't need traffic from North Korea. If you serve customers in the EU, you probably don't need visitors from 150 other countries hitting your login page.&lt;/p&gt;

&lt;p&gt;Geo-blocking at the WAF level stops unwanted traffic before it ever reaches your application. No CPU spent. No database queries wasted. No bandwidth consumed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers from My Server
&lt;/h3&gt;

&lt;p&gt;After 30 days of logging, I checked where attacks came from:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traffic Source&lt;/th&gt;
&lt;th&gt;% of Total Requests&lt;/th&gt;
&lt;th&gt;% of Attacks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target countries (where my customers are)&lt;/td&gt;
&lt;td&gt;23%&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-target countries&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;77% of my traffic came from countries I don't serve, and 92% of attacks originated from those countries.&lt;/strong&gt; Geo-blocking the non-target regions would eliminate the vast majority of malicious traffic with zero impact on real users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Geo-Blocking in SafeLine
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Go to Allow &amp;amp; Deny -&amp;gt; Custom Rules -&amp;gt; Add Rules&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Choose your approach:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Apply the rule. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Happens to Blocked Visitors
&lt;/h3&gt;

&lt;p&gt;Blocked IPs see a 403 Forbidden page. They can't reach your application at all — the WAF drops the connection at the proxy layer. Your app server never sees these requests.&lt;/p&gt;

&lt;p&gt;SafeLine logs every geo-blocked request to Attack Logs. You'll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which country the IP was from&lt;/li&gt;
&lt;li&gt;What URL they tried to access&lt;/li&gt;
&lt;li&gt;The exact timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Which Countries to Block
&lt;/h3&gt;

&lt;p&gt;Based on my 30-day log analysis and common community reports:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Almost always safe to block:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;North Korea — 0 legitimate traffic for 99.9% of sites&lt;/li&gt;
&lt;li&gt;Iran — heavy scanner activity, minimal legitimate traffic (for non-Iranian sites)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;High scanner volume, consider blocking if not your market:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Russia — #1 source of scans on most servers&lt;/li&gt;
&lt;li&gt;China — heavy automated scanning&lt;/li&gt;
&lt;li&gt;Vietnam — growing botnet activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Block with caution (some legitimate traffic):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brazil — high bot activity but also real users&lt;/li&gt;
&lt;li&gt;India — large developer community but also heavy scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What NOT to Geo-Block
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDN IPs&lt;/strong&gt; — Cloudflare, Fastly, Akamai. These proxy legitimate traffic. SafeLine handles this automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search engine crawlers&lt;/strong&gt; — Googlebot, Bingbot. You want these finding your site. SafeLine's bot detection distinguishes crawlers from attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own office/home IP&lt;/strong&gt; — obvious, but easy to forget when you add a blanket rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Can I geo-block at the city or ISP level?
&lt;/h3&gt;

&lt;p&gt;Yes. SafeLine supports country, region, city, and ISP-level filtering through its IP database. For a SaaS product, you might block certain hosting providers (DigitalOcean, Vultr) if they're being used to launch attacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will this affect my SEO?
&lt;/h3&gt;

&lt;p&gt;No. Search engine crawlers (Googlebot, Bingbot) are never blocked by geo-rules. SafeLine identifies them by user agent and verified IP ranges. Your site remains fully crawlable regardless of geo-blocking settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if a legitimate user travels to a blocked country?
&lt;/h3&gt;

&lt;p&gt;They'll need to use a VPN in an allowed country. This is a known trade-off. For most small businesses, the security gain far outweighs the edge case of a traveling customer.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try SafeLine Community Edition — free, self-hosted, and takes 5 minutes to deploy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLk&lt;/span&gt; https://waf.chaitin.com/release/latest/manager.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--en&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dashboard: &lt;code&gt;https://&amp;lt;your-server-ip&amp;gt;:9443&lt;/code&gt; | &lt;a href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What countries do you see the most scanner traffic from on your server?&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What 30 Days of WAF Logs Taught Me About Internet Attackers</title>
      <dc:creator>Lia</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:18:56 +0000</pubDate>
      <link>https://dev.to/lialiago/what-30-days-of-waf-logs-taught-me-about-internet-attackers-10c0</link>
      <guid>https://dev.to/lialiago/what-30-days-of-waf-logs-taught-me-about-internet-attackers-10c0</guid>
      <description>&lt;h3&gt;
  
  
  The Setup
&lt;/h3&gt;

&lt;p&gt;Deployed SafeLine Community Edition on a $10/month VPS in front of 3 small websites: a WordPress blog, a Node.js SaaS app, and a static portfolio. Let it run for 30 days, blocking nothing — pure observation mode. Here's what showed up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 1-3: The Welcome Party
&lt;/h3&gt;

&lt;p&gt;Within 3 hours of DNS propagation, automated scanners found the server.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack type&lt;/th&gt;
&lt;th&gt;Count (Day 1)&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WordPress path scans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;847&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/wp-admin&lt;/code&gt;, &lt;code&gt;/xmlrpc.php&lt;/code&gt;, &lt;code&gt;/wp-content/uploads/*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSH brute force probes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;312&lt;/td&gt;
&lt;td&gt;(Not via HTTP — separate, but logged by fail2ban)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.env file grabs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /.env&lt;/code&gt; targeting Laravel apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PHP unit exploit scans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; A new server is discovered by scanners within hours, not days. If you deploy a site without a WAF, you're already under attack before you finish configuring it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 4-10: The Pattern Emerges
&lt;/h3&gt;

&lt;p&gt;After the initial flood, attacks settled into patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peak attack hours:&lt;/strong&gt; 2-5 AM UTC (when most US/EU admins are asleep)&lt;br&gt;
&lt;strong&gt;Top attacking countries&lt;/strong&gt; (by IP):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Russia — 23%&lt;/li&gt;
&lt;li&gt;China — 18%&lt;/li&gt;
&lt;li&gt;United States — 14% (mostly compromised VPS)&lt;/li&gt;
&lt;li&gt;Netherlands — 9% (hosting/datacenter IPs)&lt;/li&gt;
&lt;li&gt;Vietnam — 7%&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Most targeted pages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/wp-login.php&lt;/code&gt; — 38% of all attacks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/.env&lt;/code&gt; — 12%&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/admin&lt;/code&gt; — 8%&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/graphql&lt;/code&gt; — 5% (even on the WordPress site!)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/vendor/*&lt;/code&gt; — 4%&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Day 11-20: The Persistent Ones
&lt;/h3&gt;

&lt;p&gt;Some attacks didn't give up. One IP from a Russian VPS range attempted &lt;code&gt;/wp-login.php&lt;/code&gt; with different credentials 4,287 times over 11 days — an average of 390 attempts per day. Without rate limiting, that's a brute force that will eventually succeed if your password is in any dictionary.&lt;/p&gt;

&lt;p&gt;Three different IPs tried the same Log4j payload against every URL path they scanned. They didn't know the tech stack — they just spray and pray.&lt;/p&gt;
&lt;h3&gt;
  
  
  Day 21-30: What Changed
&lt;/h3&gt;

&lt;p&gt;After 3 weeks of observation, I enabled blocking mode:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack type&lt;/th&gt;
&lt;th&gt;Before (daily avg)&lt;/th&gt;
&lt;th&gt;After (daily avg)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WordPress scans&lt;/td&gt;
&lt;td&gt;847&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.env grabs&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL injection probes&lt;/td&gt;
&lt;td&gt;203&lt;/td&gt;
&lt;td&gt;0 (blocked at WAF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XSS attempts&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total blocked&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;~1,300/day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1,300 attacks per day, blocked silently.&lt;/strong&gt; No CPU spent in application code handling them. No log noise from PHP or Node.js. They hit the WAF, get blocked, and the app never sees them.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the Attackers Never Found
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;My Node.js app's real vulnerabilities (they got blocked at the WAF before reaching Express)&lt;/li&gt;
&lt;li&gt;My database credentials (&lt;code&gt;.env&lt;/code&gt; was blocked at the path level)&lt;/li&gt;
&lt;li&gt;Any valid WordPress user (rate limiting stopped the brute force before it made progress)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  FAQ
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Is a WAF really necessary for a small site?
&lt;/h3&gt;

&lt;p&gt;If you have a domain name and an IP, scanners will find you. The question isn't whether you'll be attacked — it's whether those attacks reach your app or get blocked at the proxy. My small portfolio site saw 300+ attack attempts per day. It had no login, no database, and nothing to steal — but the scanners don't know that.&lt;/p&gt;
&lt;h3&gt;
  
  
  Do all these attacks actually work?
&lt;/h3&gt;

&lt;p&gt;Most don't, if your software is up to date. But "most" isn't 100%. A WAF catches the 1% that would work, plus the 99% that just add noise. The real value is that your application doesn't have to deal with any of it.&lt;/p&gt;
&lt;h3&gt;
  
  
  What's the first thing I should block?
&lt;/h3&gt;

&lt;p&gt;Rate limit your login page. Always. Every site has one. Every attacker tries it. One rule stops the #1 attack vector instantly.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Try SafeLine Community Edition — free, self-hosted, and takes 5 minutes to deploy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLk&lt;/span&gt; https://waf.chaitin.com/release/latest/manager.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--en&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dashboard: &lt;code&gt;https://&amp;lt;your-server-ip&amp;gt;:9443&lt;/code&gt; | &lt;a href="https://docs.waf.chaitin.com/en/home" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What do you think your server handles every day that you don't see?&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
