<pclass="section-sub"style="margin-top:1rem">From zero to a working WireGuard VPN admin panel in under five minutes.</p>
</div>
</section>
<divclass="page-content">
<divclass="container">
<h2id="prerequisites">Prerequisites</h2>
<ul>
<li>A Linux server reachable from where you’ll manage it</li>
<li><ahref="https://docs.docker.com/engine/install/">Docker</a> and <ahref="https://docs.docker.com/compose/install/">Docker Compose</a> installed</li>
<li>A domain name pointing to your server’s IP</li>
<li>Ports <strong>80</strong> and <strong>443</strong> open for Caddy; your WireGuard UDP port open (default <strong>51820</strong>)</li>
</ul>
<divclass="callout">
<p><strong>Caddy requires a valid DNS name</strong> — either internal or public — pointing to your server so it can obtain and renew SSL certificates automatically.</p>
<p>See the <ahref="#env-reference">.env reference</a> below for all available variables.</p>
</div>
<divclass="tab-panel"id="dep-step-4">
<pre><code>docker compose up -d</code></pre>
<p>Access the panel at <code>https://vpn.example.com</code>. Caddy obtains and renews SSL certificates automatically.</p>
</div>
</div>
</div>
<hr>
<h2id="env-reference">.env reference</h2>
<tableclass="env-table">
<thead>
<tr>
<th>Variable</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>SERVER_ADDRESS</td>
<td>Yes</td>
<td>DNS name or IP of your server. Must match what you type in the browser — a mismatch causes CSRF errors.</td>
</tr>
<tr>
<td>DEBUG_MODE</td>
<td>No</td>
<td>Set to <code>True</code> to enable Django debug mode. Never use in production. Default: <code>False</code>.</td>
</tr>
<tr>
<td>TIMEZONE</td>
<td>No</td>
<td>Timezone for the application. Use a value from the <ahref="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"target="_blank"rel="noopener">tz database</a>. Default: <code>America/Sao_Paulo</code>.</td>
</tr>
<tr>
<td>EXTRA_ALLOWED_HOSTS</td>
<td>No</td>
<td>Additional hostnames Django should accept, comma-separated. <code>SERVER_ADDRESS</code> is always included. Example: <code>app1.example.com,app2.example.com:8443</code>.</td>
</tr>
<tr>
<td>WIREGUARD_STATUS_CACHE_ENABLED</td>
<td>No</td>
<td>Cache WireGuard status to reduce calls to <code>wg</code>. Default: <code>True</code>.</td>
</tr>
<tr>
<td>WIREGUARD_STATUS_CACHE_REFRESH_INTERVAL</td>
<td>No</td>
<td>How often (in seconds) the cache refreshes. Allowed: <code>30</code>, <code>60</code>, <code>150</code>, <code>300</code>. Default: <code>60</code>.</td>
<td>How many cached snapshots to preload on page load (0–9). Higher values pre-populate traffic charts. Lower if the peer list feels slow. Default: <code>9</code>.</td>
</tr>
<tr>
<td>DISABLE_AUTO_APPLY</td>
<td>No</td>
<td>Disable automatic application of WireGuard and DNS configuration changes. By default, peer and DNS changes are applied immediately. Set to <code>true</code> to apply changes manually. Default: <code>false</code>.</td>
</tr>
<tr>
<td>WIREGUARD_MTU</td>
<td>No</td>
<td>Custom MTU for WireGuard interfaces (server and client configs). Only change if you know what you are doing. Must be an integer between <code>1280</code> and <code>9000</code>. After changing, re-export and redistribute all client configs — mismatched MTU can cause connectivity issues. Default: <code>1420</code>.</td>
</tr>
<tr>
<td>VPN_CLIENTS_CAN_ACCESS_DJANGO</td>
<td>No</td>
<td>Allow VPN clients to access the web interface directly via the internal interface at <code>http://ip_or_hostname:8000</code>. When enabled, the internal address including port <code>:8000</code> must be added to <code>EXTRA_ALLOWED_HOSTS</code>, otherwise Django will block the request. Default: <code>False</code>.</td>
</tr>
</tbody>
</table>
<hr>
<h2id="upgrading">Upgrading</h2>
<divclass="callout green">
<p><strong>Data is persisted in Docker volumes.</strong> Upgrading does not affect your peers, firewall rules, DNS entries, or any other configuration.</p>
</div>
<divclass="deploy-steps">
<divclass="deploy-step-card">
<divclass="deploy-step-num">01</div>
<divclass="deploy-step-body">
<divclass="deploy-step-label">Navigate to the project directory</div>
<pre><code>cd wireguard_webadmin</code></pre>
</div>
</div>
<divclass="deploy-step-card">
<divclass="deploy-step-num">02</div>
<divclass="deploy-step-body">
<divclass="deploy-step-label">Stop services and pull latest images</div>
<pre><code>docker compose down
docker compose pull</code></pre>
</div>
</div>
<divclass="deploy-step-card">
<divclass="deploy-step-num">03</div>
<divclass="deploy-step-body">
<divclass="deploy-step-label">Back up your data</div>
<li>Check all containers are running: <code>docker compose ps</code></li>
<li>Look for errors: <code>docker compose logs wireguard_webadmin</code></li>
<li>Verify <code>SERVER_ADDRESS</code> in <code>.env</code> matches exactly what you’re typing in the browser</li>
</ul>
<h3id="csrf-errors-on-login">CSRF errors on login</h3>
<p><code>SERVER_ADDRESS</code> is misconfigured. It must match the hostname (and port, if non-standard) used to access the panel. Update <code>.env</code> and restart with <code>docker compose up -d</code>.</p>
<li>Confirm the WireGuard UDP port is open on the host firewall. The default is <strong>51820</strong>, but if you’re running multiple instances each one needs its own port.</li>
<li>Make sure the UDP port range declared in <code>docker-compose.yml</code> matches what is configured in each WireGuard instance inside the panel. A mismatch means the container won’t expose the right port to the host.</li>
<li>Verify IP forwarding is enabled on the host: <code>sysctl net.ipv4.ip_forward</code></li>
</ul>
<hr>
<h2id="whats-running">What’s running</h2>
<tableclass="env-table">
<thead>
<tr><th>Service</th><th>Role</th></tr>
</thead>
<tbody>
<tr><td>wireguard-webadmin</td><td>Django application — web UI and API</td></tr>