<?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>Haproxy on inetshell</title><link>https://inet.sh/es/tags/haproxy/</link><description>Recent content in Haproxy on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Fri, 25 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/haproxy/index.xml" rel="self" type="application/rss+xml"/><item><title>pfSense: certificado Let's Encrypt con ACME y DNS de Cloudflare, y usarlo en HAProxy</title><link>https://inet.sh/es/posts/pfsense/haproxy-acme-letsencrypt-cloudflare/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/haproxy-acme-letsencrypt-cloudflare/</guid><description>Emitir un certificado de Let&amp;#39;s Encrypt en pfSense con el paquete ACME y validación DNS-01 en Cloudflare (solo API token, Account ID y Zone ID; sin la Global API Key), incluido wildcard, y colgarlo del frontend de HAProxy con reinicio automático en cada renovación. Con la trampa del filtro de IP del token y del DNS Sleep.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<ol>
<li><strong>Cloudflare</strong> → My Profile → API Tokens → <em>Create Token</em> → plantilla <em>Edit zone DNS</em>: permisos <strong>Zone → Zone → Read</strong> y <strong>Zone → DNS → Edit</strong>, <em>Zone Resources</em> limitado a tu zona. Copia el token; el <strong>Account ID</strong> y el <strong>Zone ID</strong> están en la página <em>Overview</em> de la zona.</li>
<li><strong>pfSense</strong> → System → Package Manager → instalar <strong>acme</strong> y <strong>haproxy</strong>.</li>
<li>Services → ACME → <em>Account keys</em> → Add: <em>ACME Server</em> <strong>Let&rsquo;s Encrypt Production ACME v2</strong> (o <em>Staging</em> para probar) → <em>Create new account key</em> → <em>Register</em> → Save.</li>
<li>Services → ACME → <em>Certificates</em> → Add: nombre, la cuenta, <em>Domain SAN list</em>: <code>app.example.com</code> (y <code>*.example.com</code> si quieres wildcard) con método <strong>DNS-Cloudflare</strong>, y en sus campos solo <strong>Token</strong>, <strong>Account ID</strong> y <strong>Zone ID</strong>. <em>Actions list</em>: <strong>Restart Local Service</strong> → <code>haproxy</code>. Save → <strong>Issue</strong>.</li>
<li>Services → HAProxy → <em>Frontend</em> → editar → <em>SSL Offloading</em> ✔ en la dirección de escucha → <em>Certificate</em>: el que acaba de crear ACME → Save → <em>Apply Changes</em>.</li>
</ol>
<p>Renovación automática (cron diario del paquete) y HAProxy reiniciado en cada renovación.</p>
<h2 id="cloudflare-el-token">Cloudflare: el token</h2>
<p>La <em>Global API Key</em> que piden las guías antiguas da acceso a toda la cuenta; un <strong>API token</strong> con permisos mínimos es lo correcto y es lo que el método <em>DNS-Cloudflare</em> del paquete usa cuando rellenas <code>Token</code>:</p>
<table>
  <thead>
      <tr>
          <th>Campo del paquete</th>
          <th>Valor</th>
          <th>Dónde</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Token</td>
          <td>El API token</td>
          <td>My Profile → API Tokens</td>
      </tr>
      <tr>
          <td>Account ID</td>
          <td>ID de la cuenta</td>
          <td>Zona → Overview → <em>API</em> (columna derecha)</td>
      </tr>
      <tr>
          <td>Zone ID</td>
          <td>ID de la zona</td>
          <td>Mismo sitio</td>
      </tr>
      <tr>
          <td>Key / Email</td>
          <td>Vacíos</td>
          <td>Solo para la Global API Key (no la uses)</td>
      </tr>
  </tbody>
</table>
<p>Con <code>Token</code> basta uno de los dos IDs; poner ambos no molesta. Permisos del token: <strong>Zone:Read</strong> y <strong>DNS:Edit</strong> sobre la zona concreta (<em>Include → Specific zone</em>).</p>
<p>Si activas <em>Client IP Address Filtering</em> en el token, tiene que incluir la IP pública <strong>IPv4 y también IPv6</strong> del firewall: pfSense sale por la que tenga a mano, y un token limitado a la v4 falla en cuanto la llamada a la API va por v6.</p>
<h2 id="acme-cuenta-y-certificado">ACME: cuenta y certificado</h2>
<p><strong>Cuenta.</strong> Crea dos: una contra <em>Staging</em> para probar y otra contra <em>Production</em>. Staging no tiene límites de emisión pero sus certificados no son de confianza; cuando todo funcione, cambia la cuenta del certificado a la de producción y vuelve a emitir.</p>
<p><strong>Certificado.</strong> En Services → ACME → <em>Certificates</em> → Add:</p>
<ul>
<li><em>Domain SAN list</em>: una entrada por nombre. Para un wildcard <code>*.example.com</code> la validación tiene que ser DNS-01 (HTTP-01 no lo permite). Un wildcard no cubre el apex: añade <code>example.com</code> como segunda entrada.</li>
<li><em>Method</em>: <strong>DNS-Cloudflare</strong> en cada entrada, con los tres campos.</li>
<li><em>DNS Sleep</em>: Cloudflare propaga en segundos; el valor por defecto sobra. Si la emisión falla por <code>TXT not found</code>, súbelo a <code>120</code>.</li>
<li><em>Key</em>: RSA 2048 o ECDSA 256; ambos van bien con HAProxy.</li>
<li><em>Actions list</em>: <strong>Restart Local Service</strong> → <strong>haproxy</strong>. Sin esto, HAProxy sigue sirviendo el certificado viejo hasta que alguien lo reinicie, aunque ACME ya haya renovado.</li>
</ul>
<p><strong>Issue</strong> desde la lista de certificados. El resultado aparece en System → Certificates con el nombre que le diste; ahí lo verá HAProxy.</p>
<h2 id="haproxy-usar-el-certificado">HAProxy: usar el certificado</h2>
<p>Services → HAProxy:</p>
<ol>
<li><em>Settings</em>: activar HAProxy, y en <em>Max SSL Diffie-Hellman size</em> deja 2048 o más.</li>
<li><em>Frontend</em> → tu frontend HTTPS → <em>External address</em>: puerto <code>443</code>, <strong>SSL Offloading</strong> ✔ → sección <em>SSL Offloading</em> → <strong>Certificate</strong>: el de ACME. Para varios dominios en el mismo frontend, <em>Additional certificates</em> con los demás; HAProxy elige por SNI.</li>
<li>Save → <strong>Apply Changes</strong>.</li>
</ol>
<p>Prueba desde fuera:</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">openssl s_client -connect app.example.com:443 -servername app.example.com &lt;/dev/null 2&gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> openssl x509 -noout -issuer -dates
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>issuer=C = US, O = Let's Encrypt</code> y <code>notAfter</code> a 90 días.</p>
<p>De regalo, la GUI de pfSense con el mismo certificado: System → Advanced → <em>Admin Access</em> → <em>SSL/TLS Certificate</em>, y una segunda acción en el certificado ACME: <strong>Shell Command</strong> <code>/etc/rc.restart_webgui</code>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>Cloudflare en modo proxy (nube naranja)</strong> no afecta a DNS-01, pero sí a cómo llega el tráfico: si el registro <code>A</code> está proxificado, HAProxy ve la IP de Cloudflare como origen. Para un servicio interno detrás de pfSense, nube gris.</li>
<li><strong><code>Issue</code> falla con <code>Invalid request headers</code> o <code>Authentication error</code></strong>: token sin permiso sobre esa zona, filtro de IP del token, o Zone ID de otra zona.</li>
<li><strong>Rate limits de producción</strong>: cinco certificados por conjunto de nombres a la semana. Prueba en staging.</li>
<li><strong>El certificado se renueva; HAProxy no lo recarga solo.</strong> La acción de reinicio es obligatoria. Comprueba en Services → ACME que la última renovación tiene fecha y en <code>openssl s_client</code> que <code>notAfter</code> avanzó.</li>
<li><strong>Los ACME se renuevan a los 2/3 de vida</strong> (unos 60 días). Si ves el cron ejecutándose y nada cambia antes de eso, es normal.</li>
</ul>
<p>Referencia: <a href="https://docs.netgate.com/pfsense/en/latest/packages/acme/index.html">ACME package</a> en la documentación de Netgate y la <a href="https://github.com/acmesh-official/acme.sh/wiki/dnsapi">lista de proveedores DNS de acme.sh</a> (sección <code>dns_cf</code>).</p>
]]></content:encoded></item></channel></rss>