<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>OPNsense on inetshell</title><link>https://inet.sh/en/posts/opnsense/</link><description>Recent content in OPNsense on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/posts/opnsense/index.xml" rel="self" type="application/rss+xml"/><item><title>OPNsense: port-forward unreachable from the same WAN subnet (reply-to)</title><link>https://inet.sh/en/posts/opnsense/reply-to-same-wan-subnet/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/opnsense/reply-to-same-wan-subnet/</guid><description>Why an OPNsense port-forward works from the internet but times out from an IP in the same /24 as the WAN, and how “Disable reply-to on WAN rules” fixes it.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p><strong>Firewall → Settings → Advanced → Disable reply-to on WAN rules</strong> → check → <strong>Save</strong> → <strong>Apply</strong>.</p>
<p>Symptom: a port-forward or WAN rule answers from any internet IP, but from a host that lives in <strong>the same subnet as the firewall&rsquo;s WAN</strong> everything times out (SSH, HTTPS, even <code>ping</code>), even though the rule says <code>from any</code>.</p>
<p>With a single WAN this is safe: the firewall stops forcing replies through the gateway and goes back to its routing table.</p>
<h2 id="the-setup">The setup</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">                203.0.113.0/24  (same L2 segment)
</span></span><span class="line"><span class="cl">   ┌────────────────────┬────────────────────┬────────────────────┐
</span></span><span class="line"><span class="cl">   │                    │                    │                    │
</span></span><span class="line"><span class="cl"> gateway             fw01 (OPNsense)      neighbor             internet
</span></span><span class="line"><span class="cl"> 203.0.113.1         WAN 203.0.113.40     203.0.113.50         (via gateway)
</span></span><span class="line"><span class="cl">                        │ rdr :22 → 192.168.1.10
</span></span><span class="line"><span class="cl">                        ▼
</span></span><span class="line"><span class="cl">                     LAN server
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>From the internet: <code>ssh -p 22 203.0.113.40</code> reaches the LAN server. Fine.</li>
<li>From <code>neighbor</code> (203.0.113.50): every port times out.</li>
</ul>
<h2 id="what-reply-to-does">What <code>reply-to</code> does</h2>
<p>OPNsense generates the rules on WAN-type interfaces (those with a gateway) with <code>reply-to</code> pointing at that gateway:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -sr <span class="p">|</span> grep reply-to
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 reply-to (vtnet0 203.0.113.1) inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>reply-to</code> is a <strong>pf</strong> option. When a packet comes in and creates a state, pf records in that state that <strong>every reply</strong> must leave through <code>vtnet0</code> toward <code>203.0.113.1</code>. Those replies skip the routing table: pf picks the next hop on its own.</p>
<p>It exists for <strong>multi-WAN</strong>. If you have two providers and a client comes in on WAN2, the reply must leave through WAN2. If it leaves through the default route (WAN1), it carries a source IP that provider 1 does not recognize and gets lost. <code>reply-to</code> guarantees a symmetric return path without thinking about routes.</p>
<h2 id="why-it-breaks-neighbors">Why it breaks neighbors</h2>
<p>For an internet client, &ldquo;reply to the gateway&rdquo; is exactly what the routing table would do anyway, so nothing looks wrong.</p>
<p>For the neighbor it is different. Its IP (203.0.113.50) is on a <strong>connected network</strong> of the firewall. Without <code>reply-to</code>, the firewall would ARP for its MAC and answer directly. With <code>reply-to</code>, the SYN-ACK leaves with the <strong>gateway&rsquo;s MAC</strong> as destination:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># on the OPNsense box (csh shell: wrap in sh -c)</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;tcpdump -lnei vtnet0 host 203.0.113.50 and tcp port 22&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;neighbor-mac&gt; &gt; &lt;fw01-mac&gt;          203.0.113.50.49612 &gt; 203.0.113.40.22: Flags [S]    ← arrives directly
</span></span><span class="line"><span class="cl">&lt;fw01-mac&gt;     &gt; 00:00:5e:00:01:&lt;vrid&gt; 203.0.113.40.22 &gt; 203.0.113.50.49612: Flags [S.]   ← leaves to the gateway
</span></span></code></pre></td></tr></table>
</div>
</div><p>(<code>00:00:5e:00:01:xx</code> is the VRRP virtual MAC: the gateway was a redundant pair.)</p>
<p>From there it depends on the gateway. To it, the packet arrives on the same interface it would have to leave through, and if it runs a stateful filter it sees a SYN-ACK for a connection whose SYN it never saw. In the lab, the gateway simply did not send it back onto the segment. On the neighbor you only see retried SYNs and no <code>[S.]</code>.</p>
<p>The fingerprint on the firewall is a half-open state:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pfctl -ss <span class="p">|</span> grep 203.0.113.50
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">all tcp 192.168.1.10:22 (203.0.113.40:22) &lt;- 203.0.113.50:57792   SYN_SENT:ESTABLISHED
</span></span></code></pre></td></tr></table>
</div>
</div><p>The server already answered (<code>ESTABLISHED</code>) and the client is still waiting (<code>SYN_SENT</code>): <strong>the reply is lost on the way back</strong>, not on the way in. The filter is not blocking anything.</p>
<h2 id="what-the-fix-changes">What the fix changes</h2>
<p>With <strong>Disable reply-to on WAN rules</strong> checked, OPNsense regenerates the rules without <code>reply-to</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pass in quick on vtnet0 inet proto tcp from any to 192.168.1.10 port = ssh flags S/SA keep state
</span></span></code></pre></td></tr></table>
</div>
</div><p>The state no longer pins the next hop, so replies go through the normal routing table:</p>
<table>
  <thead>
      <tr>
          <th>Reply destination</th>
          <th>With <code>reply-to</code></th>
          <th>Without <code>reply-to</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Internet client</td>
          <td>gateway</td>
          <td>gateway (default route)</td>
      </tr>
      <tr>
          <td>Neighbor in 203.0.113.0/24</td>
          <td>gateway ✗</td>
          <td>direct via ARP ✓</td>
      </tr>
      <tr>
          <td>Client that came in on another WAN (multi-WAN)</td>
          <td>its gateway ✓</td>
          <td>default route ✗</td>
      </tr>
  </tbody>
</table>
<p>The last row is the cost. With multi-WAN, do not disable it globally. Instead, drop <code>reply-to</code> only on the affected port-forward&rsquo;s rule: in the rule, <strong>Advanced → reply-to</strong>. Check which options your version offers.</p>
<h2 id="verify">Verify</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># on the OPNsense box</span>
</span></span><span class="line"><span class="cl">sh -c <span class="s2">&#34;pfctl -sr | grep -c reply-to; grep -n disablereplyto /conf/config.xml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># expected: 0  and  &lt;disablereplyto&gt;yes&lt;/disablereplyto&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># from the neighbor</span>
</span></span><span class="line"><span class="cl">nc -z -w5 203.0.113.40 <span class="m">22</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> open
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="traps">Traps</h2>
<ul>
<li><strong>&ldquo;Disable force gateway&rdquo; is not this.</strong> It sits on the same page with a similar name, but it only affects traffic <strong>originated by the firewall itself</strong> (DNS, NTP, updates). It does not touch replies to inbound traffic.</li>
<li><strong>A static route on the gateway does not help.</strong> The gateway already has the subnet as connected; the problem is that it will not send the packet back onto the same segment, not that it does not know the way.</li>
<li><strong>csh shell.</strong> Over SSH, OPNsense drops you into <code>csh</code>, and things like <code>2&gt;/dev/null</code> fail with <code>Ambiguous output redirect</code>. Use <code>sh -c &quot;...&quot;</code> or <code>ssh root@fw01 sh &lt;&lt;'EOF'</code>.</li>
<li><strong>Test every port, not one.</strong> If only one port fails it is a different problem (rule, NAT, service). If they all fail, <code>ping</code> included, and only from the same subnet, suspect <code>reply-to</code>.</li>
</ul>
<h2 id="see-also">See also</h2>
<ul>
<li>OPNsense docs, firewall settings: <a href="https://docs.opnsense.org/manual/firewall_settings.html">https://docs.opnsense.org/manual/firewall_settings.html</a></li>
<li><code>pf.conf(5)</code>, <code>route-to</code> / <code>reply-to</code> options: <a href="https://man.freebsd.org/cgi/man.cgi?query=pf.conf">https://man.freebsd.org/cgi/man.cgi?query=pf.conf</a></li>
</ul>
]]></content:encoded></item></channel></rss>