From 1756eca2deb748cd0e1e2c37a9c7db71041780f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 Oct 2025 12:18:05 +0000 Subject: [PATCH] Deployed 9e6ad98 to master with MkDocs 1.6.1 and mike 2.1.3 --- master/404.html | 2 +- master/assets/images/wgportal_dark.png | Bin 0 -> 133082 bytes master/assets/images/wgportal_light.png | Bin 0 -> 131918 bytes .../configuration/examples/index.html | 4 +- .../configuration/overview/index.html | 4 +- .../getting-started/binaries/index.html | 4 +- .../getting-started/docker/index.html | 4 +- .../getting-started/helm/index.html | 4 +- .../getting-started/reverse-proxy/index.html | 4 +- .../getting-started/sources/index.html | 4 +- .../monitoring/prometheus/index.html | 4 +- master/documentation/overview/index.html | 2 +- .../documentation/rest-api/api-doc/index.html | 2 +- master/documentation/upgrade/v1/index.html | 4 +- .../documentation/usage/backends/index.html | 4 +- master/documentation/usage/general/index.html | 4 +- master/documentation/usage/ldap/index.html | 4 +- .../documentation/usage/security/index.html | 4 +- .../documentation/usage/webhooks/index.html | 4 +- master/index.html | 57 +++++++++++- master/javascript/img-comparison-slider.js | 2 + .../javascript/img-comparison-slider.js.map | 1 + master/search/search_index.json | 2 +- master/sitemap.xml | 34 +++---- master/sitemap.xml.gz | Bin 356 -> 356 bytes master/stylesheets/img-comparison-slider.css | 15 +++ master/theme-overrides/layouts/home.html | 86 +++++++++++++++++- 27 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 master/assets/images/wgportal_dark.png create mode 100644 master/assets/images/wgportal_light.png create mode 100644 master/javascript/img-comparison-slider.js create mode 100644 master/javascript/img-comparison-slider.js.map create mode 100644 master/stylesheets/img-comparison-slider.css diff --git a/master/404.html b/master/404.html index a3cc17c..3e00153 100644 --- a/master/404.html +++ b/master/404.html @@ -1 +1 @@ - WireGuard Portal

404 - Not found

\ No newline at end of file + WireGuard Portal

404 - Not found

\ No newline at end of file diff --git a/master/assets/images/wgportal_dark.png b/master/assets/images/wgportal_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7260037f8faa147e1c200697185c55ec9fc8b2bf GIT binary patch literal 133082 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≤yiy+0w0l2MU zr>NRvca4l}Z{N=Qk;Fw=4-Idq7Yds3-X-=y)c-vX%nwAzSF3!ae&Z4 z0}A7k;zHbO*Ib{!pFe+Ygpm^Tus4;%J${HLJT3!8#gN>tY*JgKVSYA%9C7Ympu@^!I8X6iweKOMDAIHkm zGb`1x@Adnau2Li4Nr+_D?Y-x+7Si6HVuq+|_l341Nlf};@QoX^kpq?@9EhQPi}9CxHEZAS@axtG!nXr$?%#K`Q)QH)G)nB7AqXS9WNu46z27ScZ6GX(nuEDX zbxln}V}qGFzHVF5)}x|!Y_wz7L9=6E=Ho0i2i7;&&gLxGU|x<*6gKwt%Rh7d3eeV9 zMn>upzIRY4;J86u1-U7L+%nNy0d8*B1Q3qVSOQ!dCL&SR*N-zr*kburAzTx}Fz)b2 zSeIB?rl=>3_HmOouB<_l<5?B*Sh%OnTjOTb*O)MmR6b9j*)z*#% zs=@*l^?n>Aj9y+|P@QOL#ck-%&d%}&?RaV|AOW_qn7>+fgyY=lE}N!@c5|$XJG;Bl z+|(%6iwPRmR#`Gmcir9FfZE}5w>xy>9GkuipvkW@>*Kxv0SV*v!8mpDle^Nu&`?Yg zRy(^FR^;^bO#Wt>wv5|fPrHh1b@&oAzz0?#m2#(e4&OBOp)SC`wl08{T!qKu#RL!t zK1svtfgvR&d3m;Wb}i{xJ=oI++NXHb^f8Rff2OLJOg8?KSYSCEzpd@A?uQT)me6lX zj`^x1A+ctWc>(W@18(Dzrbb0sd5ULU=vi*cAOW1(43N5beQ)I)C_4%#<5Ozw7eg?o zr(zmu+)Vwwy_D4s z0=L|D*ZH5~DMjuDA3ss@7Y}`DH!K8j5`mC?mpo<)QQ<&(IUi9#x@8%Majsi#mi4QU z6Go^)CiT2RDkhS+vjIm7k(*9=|LoX=9xfoUe&=c>>my6{QSYNI5#O{s4f84sCjC|$!AC~-qCnGWc!>d1V;EmLee}w_` v6?n%?`OlXea8L^TZ=wNUI{*J)zKi_z_2~ET+s^c#?tr?=UFBlM`_KLhtOGE_ literal 0 HcmV?d00001 diff --git a/master/assets/images/wgportal_light.png b/master/assets/images/wgportal_light.png new file mode 100644 index 0000000000000000000000000000000000000000..9b76aa6bfb9f737ef7ec0512905296d2b07edb4c GIT binary patch literal 131918 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 Q-@Z#t<=*War3bJ63tbd$V*mgE literal 0 HcmV?d00001 diff --git a/master/documentation/configuration/examples/index.html b/master/documentation/configuration/examples/index.html index 83db4d7..8f8d424 100644 --- a/master/documentation/configuration/examples/index.html +++ b/master/documentation/configuration/examples/index.html @@ -1,4 +1,4 @@ - Examples - WireGuard Portal

Examples

Below are some sample YAML configurations demonstrating how to override some default values.

Basic

core:
+ Examples - WireGuard Portal      

Examples

Below are some sample YAML configurations demonstrating how to override some default values.

Basic

core:
   admin_user: test@example.com
   admin_password: password
   admin_api_token: super-s3cr3t-api-token-or-a-UUID
@@ -192,4 +192,4 @@
         admin_group_regex: ^admin-group-name$
       registration_enabled: true
       log_user_info: true
-

For more information, check out the usage documentation (e.g. General Configuration or Backends Configuration).

\ No newline at end of file +

For more information, check out the usage documentation (e.g. General Configuration or Backends Configuration).

\ No newline at end of file diff --git a/master/documentation/configuration/overview/index.html b/master/documentation/configuration/overview/index.html index c7062c8..c59fb9a 100644 --- a/master/documentation/configuration/overview/index.html +++ b/master/documentation/configuration/overview/index.html @@ -1,4 +1,4 @@ - Overview - WireGuard Portal

Overview

This page provides an overview of all available configuration options for WireGuard Portal.

You can supply these configurations in a YAML file when starting the Portal. The path of the configuration file defaults to config/config.yaml (or config/config.yml) in the working directory of the executable.
It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG. For example: WG_PORTAL_CONFIG=/etc/wg-portal/config.yaml ./wg-portal.
Also, environment variable substitution in the config file is supported. Refer to the syntax.

Configuration examples are available on the Examples page.

Default configuration
core:
+ Overview - WireGuard Portal      

Overview

This page provides an overview of all available configuration options for WireGuard Portal.

You can supply these configurations in a YAML file when starting the Portal. The path of the configuration file defaults to config/config.yaml (or config/config.yml) in the working directory of the executable.
It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG. For example: WG_PORTAL_CONFIG=/etc/wg-portal/config.yaml ./wg-portal.
Also, environment variable substitution in the config file is supported. Refer to the syntax.

Configuration examples are available on the Examples page.

Default configuration
core:
   admin_user: admin@wgportal.local
   admin_password: wgportal-default
   admin_api_token: ""
@@ -89,4 +89,4 @@
 

encryption_passphrase

  • Default: (empty)
  • Description: Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set. Important: Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward. New or updated records will be encrypted; existing data remains in plaintext until it’s next modified.

Statistics

Controls how WireGuard Portal collects and reports usage statistics, including ping checks and Prometheus metrics.

use_ping_checks

  • Default: true
  • Description: Enable periodic ping checks to verify that peers remain responsive.

ping_check_workers

  • Default: 10
  • Description: Number of parallel worker processes for ping checks.

ping_unprivileged

  • Default: false
  • Description: If false, ping checks run without root privileges. This is currently considered BETA.

ping_check_interval

  • Default: 1m
  • Description: Interval between consecutive ping checks for all peers. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.

data_collection_interval

  • Default: 1m
  • Description: Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.

collect_interface_data

  • Default: true
  • Description: If true, collects interface-level data (bytes in/out) for monitoring and statistics.

collect_peer_data

  • Default: true
  • Description: If true, collects peer-level data (bytes, last handshake, endpoint, etc.).

collect_audit_data

  • Default: true
  • Description: If true, logs certain portal events (such as user logins) to the database.

listening_address

  • Default: :8787
  • Description: Address and port for the integrated Prometheus metric server (e.g., :8787 or 127.0.0.1:8787).

Mail

Options for configuring email notifications or sending peer configurations via email.

host

  • Default: 127.0.0.1
  • Description: Hostname or IP of the SMTP server.

port

  • Default: 25
  • Description: Port number for the SMTP server.

encryption

  • Default: none
  • Description: SMTP encryption type. Valid values: none, tls, starttls.

cert_validation

  • Default: true
  • Description: If true, validate the SMTP server certificate (relevant if encryption = tls).

username

  • Default: (empty)
  • Description: Optional SMTP username for authentication.

password

  • Default: (empty)
  • Description: Optional SMTP password for authentication.

auth_type

  • Default: plain
  • Description: SMTP authentication type. Valid values: plain, login, crammd5.

from

  • Default: Wireguard Portal <noreply@wireguard.local>
  • Description: The default "From" address when sending emails.
  • Default: false
  • Description: If true, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.

Auth

WireGuard Portal supports multiple authentication strategies, including OpenID Connect (oidc), OAuth (oauth), Passkeys (webauthn) and LDAP (ldap). Each can have multiple providers configured. Below are the relevant keys.

Some core authentication options are shared across all providers, while others are specific to each provider type.

min_password_length

  • Default: 16
  • Description: Minimum password length for local authentication. This is not enforced for LDAP authentication. The default admin password strength is also enforced by this setting.
  • Important: The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.

hide_login_form

  • Default: false
  • Description: If true, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method. If no social login providers are configured, the login form is always shown, regardless of this setting.
  • Important: You can still access the login form by adding the ?all query parameter to the login URL (e.g. https://wg.portal/#/login?all).

OIDC

The oidc array contains a list of OpenID Connect providers. Below are the properties for each OIDC provider entry inside auth.oidc:

provider_name

  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.

display_name

  • Default: (empty)
  • Description: A user-friendly name shown on the login page (e.g., "Login with Google").

base_url

  • Default: (empty)
  • Description: The OIDC provider’s base URL (e.g., https://accounts.google.com).

client_id

  • Default: (empty)
  • Description: The OAuth client ID from the OIDC provider.

client_secret

  • Default: (empty)
  • Description: The OAuth client secret from the OIDC provider.

extra_scopes

  • Default: (empty)
  • Description: A list of additional OIDC scopes (e.g., profile, email).

allowed_domains

  • Default: (empty)
  • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.

field_map

  • Default: (empty)
  • Description: Maps OIDC claims to WireGuard Portal user fields.
  • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

    Field Typical OIDC Claim Explanation
    user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it’s unique.
    email email The user’s email address as provided by the IdP. Not always verified, depending on IdP settings.
    firstname given_name The user’s first name, typically provided by the IdP in the given_name claim.
    lastname family_name The user’s last (family) name, typically provided by the IdP in the family_name claim.
    phone phone_number The user’s phone number. This may require additional scopes/permissions from the IdP to access.
    department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute).
    is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership.
    user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.

admin_mapping

  • Default: (empty)
  • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
    • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string "true" (^true$).
    • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.

registration_enabled

  • Default: (empty)
  • Description: If true, a new user will be created in WireGuard Portal if not already present.

log_user_info

  • Default: (empty)
  • Description: If true, OIDC user data is logged at the trace level upon login (for debugging).

OAuth

The oauth array contains a list of plain OAuth2 providers. Below are the properties for each OAuth provider entry inside auth.oauth:

provider_name

  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.

display_name

  • Default: (empty)
  • Description: A user-friendly name shown on the login page.

client_id

  • Default: (empty)
  • Description: The OAuth client ID for the provider.

client_secret

  • Default: (empty)
  • Description: The OAuth client secret for the provider.

auth_url

  • Default: (empty)
  • Description: URL of the authentication endpoint.

token_url

  • Default: (empty)
  • Description: URL of the token endpoint.

user_info_url

  • Default: (empty)
  • Description: URL of the user information endpoint.

scopes

  • Default: (empty)
  • Description: A list of OAuth scopes.

allowed_domains

  • Default: (empty)
  • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.

field_map

  • Default: (empty)
  • Description: Maps OAuth attributes to WireGuard Portal fields.
  • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

    Field Typical Claim Explanation
    user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it’s unique.
    email email The user’s email address as provided by the IdP. Not always verified, depending on IdP settings.
    firstname given_name The user’s first name, typically provided by the IdP in the given_name claim.
    lastname family_name The user’s last (family) name, typically provided by the IdP in the family_name claim.
    phone phone_number The user’s phone number. This may require additional scopes/permissions from the IdP to access.
    department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute).
    is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership.
    user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.

admin_mapping

  • Default: (empty)
  • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
  • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string "true" (^true$).
  • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.

registration_enabled

  • Default: (empty)
  • Description: If true, new users are created automatically on successful login.

log_user_info

  • Default: (empty)
  • Description: If true, logs user info at the trace level upon login.

LDAP

The ldap array contains a list of LDAP authentication providers. Below are the properties for each LDAP provider entry inside auth.ldap:

provider_name

  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.

url

  • Default: (empty)
  • Description: The LDAP server URL (e.g., ldap://srv-ad01.company.local:389).

start_tls

  • Default: (empty)
  • Description: If true, use STARTTLS to secure the LDAP connection.

cert_validation

  • Default: (empty)
  • Description: If true, validate the LDAP server’s TLS certificate.

tls_certificate_path

  • Default: (empty)
  • Description: Path to a TLS certificate if needed for LDAP connections.

tls_key_path

  • Default: (empty)
  • Description: Path to the corresponding TLS certificate key.

base_dn

  • Default: (empty)
  • Description: The base DN for user searches (e.g., DC=COMPANY,DC=LOCAL).

bind_user

  • Default: (empty)
  • Description: The bind user for LDAP (e.g., company\\ldap_wireguard or ldap_wireguard@company.local).

bind_pass

  • Default: (empty)
  • Description: The bind password for LDAP authentication.

field_map

  • Default: (empty)
  • Description: Maps LDAP attributes to WireGuard Portal fields.

    • Available fields: user_identifier, email, firstname, lastname, phone, department, memberof.
    WireGuard Portal Field Typical LDAP Attribute Short Description
    user_identifier sAMAccountName / uid Uniquely identifies the user within the LDAP directory.
    email mail / userPrincipalName Stores the user's primary email address.
    firstname givenName Contains the user's first (given) name.
    lastname sn Contains the user's last (surname) name.
    phone telephoneNumber / mobile Holds the user's phone or mobile number.
    department departmentNumber / ou Specifies the department or organizational unit of the user.
    memberof memberOf Lists the groups and roles to which the user belongs.

login_filter

  • Default: (empty)
  • Description: An LDAP filter to restrict which users can log in. Use {{login_identifier}} to insert the username. For example:
    (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))
     
  • Important: The login_filter must always be a valid LDAP filter. It should at most return one user. If the filter returns multiple or no users, the login will fail.

admin_group

  • Default: (empty)
  • Description: A specific LDAP group whose members are considered administrators in WireGuard Portal. For example:
    CN=WireGuardAdmins,OU=Some-OU,DC=YOURDOMAIN,DC=LOCAL
     

sync_interval

  • Default: (empty)
  • Description: How frequently (in duration, e.g. 30m) to synchronize users from LDAP. Empty or 0 disables sync. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration. Only users that match the sync_filter are synchronized, if disable_missing is true, users not found in LDAP are disabled.

sync_filter

  • Default: (empty)
  • Description: An LDAP filter to select which users get synchronized into WireGuard Portal. For example:
    (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
    -

disable_missing

  • Default: (empty)
  • Description: If true, any user not found in LDAP (during sync) is disabled in WireGuard Portal.

auto_re_enable

  • Default: (empty)
  • Description: If true, users that where disabled because they were missing (see disable_missing) will be re-enabled once they are found again.

registration_enabled

  • Default: (empty)
  • Description: If true, new user accounts are created in WireGuard Portal upon first login.

log_user_info

  • Default: (empty)
  • Description: If true, logs LDAP user data at the trace level upon login.

WebAuthn (Passkeys)

The webauthn section contains configuration options for WebAuthn authentication (passkeys).

enabled

  • Default: true
  • Description: If true, Passkey authentication is enabled. If false, WebAuthn is disabled. Users are encouraged to use Passkeys for secure authentication instead of passwords. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.

Web

The web section contains configuration options for the web server, including the listening address, session management, and CSRF protection. It is important to specify a valid external_url for the web server, especially if you are using a reverse proxy. Without a valid external_url, the login process may fail due to CSRF protection.

listening_address

  • Default: :8888
  • Description: The listening address and port for the web server (e.g., :8888 to bind on all interfaces or 127.0.0.1:8888 to bind only on the loopback interface). Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.

external_url

  • Default: http://localhost:8888
  • Description: The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects.
    Important: If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.

site_company_name

  • Default: WireGuard Portal
  • Description: The company name that is shown at the bottom of the web frontend.

site_title

  • Default: WireGuard Portal
  • Description: The title that is shown in the web frontend.

session_identifier

  • Default: wgPortalSession
  • Description: The session identifier for the web frontend.

session_secret

  • Default: very_secret
  • Description: The session secret for the web frontend.

csrf_secret

  • Default: extremely_secret
  • Description: The CSRF secret.

request_logging

  • Default: false
  • Description: Log all HTTP requests.

expose_host_info

  • Default: false
  • Description: Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.

cert_file

  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate file.

key_file

  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate key file.

Webhook

The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. Further details can be found in the usage documentation.

url

  • Default: (empty)
  • Description: The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.

authentication

  • Default: (empty)
  • Description: The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: Bearer <token>.

timeout

  • Default: 10s
  • Description: The timeout for the webhook request. If the request takes longer than this, it is aborted.
\ No newline at end of file +

disable_missing

  • Default: (empty)
  • Description: If true, any user not found in LDAP (during sync) is disabled in WireGuard Portal.

auto_re_enable

  • Default: (empty)
  • Description: If true, users that where disabled because they were missing (see disable_missing) will be re-enabled once they are found again.

registration_enabled

  • Default: (empty)
  • Description: If true, new user accounts are created in WireGuard Portal upon first login.

log_user_info

  • Default: (empty)
  • Description: If true, logs LDAP user data at the trace level upon login.

WebAuthn (Passkeys)

The webauthn section contains configuration options for WebAuthn authentication (passkeys).

enabled

  • Default: true
  • Description: If true, Passkey authentication is enabled. If false, WebAuthn is disabled. Users are encouraged to use Passkeys for secure authentication instead of passwords. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.

Web

The web section contains configuration options for the web server, including the listening address, session management, and CSRF protection. It is important to specify a valid external_url for the web server, especially if you are using a reverse proxy. Without a valid external_url, the login process may fail due to CSRF protection.

listening_address

  • Default: :8888
  • Description: The listening address and port for the web server (e.g., :8888 to bind on all interfaces or 127.0.0.1:8888 to bind only on the loopback interface). Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.

external_url

  • Default: http://localhost:8888
  • Description: The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects.
    Important: If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.

site_company_name

  • Default: WireGuard Portal
  • Description: The company name that is shown at the bottom of the web frontend.

site_title

  • Default: WireGuard Portal
  • Description: The title that is shown in the web frontend.

session_identifier

  • Default: wgPortalSession
  • Description: The session identifier for the web frontend.

session_secret

  • Default: very_secret
  • Description: The session secret for the web frontend.

csrf_secret

  • Default: extremely_secret
  • Description: The CSRF secret.

request_logging

  • Default: false
  • Description: Log all HTTP requests.

expose_host_info

  • Default: false
  • Description: Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.

cert_file

  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate file.

key_file

  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate key file.

Webhook

The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. Further details can be found in the usage documentation.

url

  • Default: (empty)
  • Description: The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.

authentication

  • Default: (empty)
  • Description: The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: Bearer <token>.

timeout

  • Default: 10s
  • Description: The timeout for the webhook request. If the request takes longer than this, it is aborted.
\ No newline at end of file diff --git a/master/documentation/getting-started/binaries/index.html b/master/documentation/getting-started/binaries/index.html index af8250e..b1729ff 100644 --- a/master/documentation/getting-started/binaries/index.html +++ b/master/documentation/getting-started/binaries/index.html @@ -1,6 +1,6 @@ - Binaries - WireGuard Portal

Binaries

Starting from v2, each release includes compiled binaries for supported platforms. These binary versions can be manually downloaded and installed.

Download

Make sure that you download the correct binary for your architecture. The available binaries are:

  • wg-portal_linux_amd64 - Linux x86_64
  • wg-portal_linux_arm64 - Linux ARM 64-bit
  • wg-portal_linux_arm_v7 - Linux ARM 32-bit

With curl:

curl -L -o wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64 
+ Binaries - WireGuard Portal      

Binaries

Starting from v2, each release includes compiled binaries for supported platforms. These binary versions can be manually downloaded and installed.

Download

Make sure that you download the correct binary for your architecture. The available binaries are:

  • wg-portal_linux_amd64 - Linux x86_64
  • wg-portal_linux_arm64 - Linux ARM 64-bit
  • wg-portal_linux_arm_v7 - Linux ARM 32-bit

With curl:

curl -L -o wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64 
 

With wget:

wget -O wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64
 

with gh cli:

gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'
 

Install

sudo mkdir -p /opt/wg-portal
 sudo install wg-portal /opt/wg-portal/
-

Unreleased versions (master branch builds)

Unreleased versions can be fetched directly from the artifacts section of the GitHub Workflow.

\ No newline at end of file +

Unreleased versions (master branch builds)

Unreleased versions can be fetched directly from the artifacts section of the GitHub Workflow.

\ No newline at end of file diff --git a/master/documentation/getting-started/docker/index.html b/master/documentation/getting-started/docker/index.html index 558485a..9ca198b 100644 --- a/master/documentation/getting-started/docker/index.html +++ b/master/documentation/getting-started/docker/index.html @@ -1,4 +1,4 @@ - Docker - WireGuard Portal

Docker

Image Usage

The WireGuard Portal Docker image is available on both Docker Hub and GitHub Container Registry. It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.

This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the linuxserver/wireguard Docker image.

The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.

A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:

---
+ Docker - WireGuard Portal      

Docker

Image Usage

The WireGuard Portal Docker image is available on both Docker Hub and GitHub Container Registry. It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.

This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the linuxserver/wireguard Docker image.

The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.

A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:

---
 services:
   wg-portal:
     image: wgportal/wg-portal:v2
@@ -80,4 +80,4 @@
   config_storage_path: /etc/wireguard/
 

Image Versioning

All images are hosted on Docker Hub at https://hub.docker.com/r/wgportal/wg-portal or in the GitHub Container Registry.

Version 2 is the current stable release. Version 1 has moved to legacy status and is no longer recommended.

There are three types of tags in the repository:

Semantic versioned tags

For example, 2.0.0-rc.1 or v2.0.0-rc.1.

These are official releases of WireGuard Portal. For production deployments of WireGuard Portal, we strongly recommend using one of these versioned tags instead of the latest or canary tags.

There are different types of these tags:

  • Major version tags: v2 or 2. These tags always refer to the latest image for WireGuard Portal version 2.
  • Minor version tags: v2.x or 2.0. These tags always refer to the latest image for WireGuard Portal version 2.x.
  • Specific version tags (patch version): v2.0.0 or 2.0.0. These tags denote a very specific release. They correspond to the GitHub tags that we make, and you can see the release notes for them here: https://github.com/h44z/wg-portal/releases. Once these tags for a specific version show up in the Docker repository, they will never change.

The latest tag

The lastest tag is the latest stable release of WireGuard Portal. For version 2, this is the same as the v2 tag.

The master tag

This is the most recent build to the main branch! It changes a lot and is very unstable.

We recommend that you don't use it except for development purposes or to test the latest features.

Configuration

You can configure WireGuard Portal using a YAML configuration file. The filepath of the YAML configuration file defaults to /app/config/config.yaml. It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG.

By default, WireGuard Portal uses an SQLite database. The database is stored in /app/data/sqlite.db.

You should mount those directories as a volume:

  • /app/data
  • /app/config

A detailed description of the configuration options can be found here.

If you want to access configuration files in wg-quick format, you can mount the /etc/wireguard directory inside the container to a location of your choice. Also enable the config_storage_path option in the configuration file:

advanced:
   config_storage_path: /etc/wireguard
-

\ No newline at end of file +

\ No newline at end of file diff --git a/master/documentation/getting-started/helm/index.html b/master/documentation/getting-started/helm/index.html index 8952e72..d247fa7 100644 --- a/master/documentation/getting-started/helm/index.html +++ b/master/documentation/getting-started/helm/index.html @@ -1,2 +1,2 @@ - Helm - WireGuard Portal

Helm

Installing the Chart

To install the chart with the release name wg-portal:

helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
-

This command deploy wg-portal on the Kubernetes cluster in the default configuration. The Values section lists the parameters that can be configured during installation.

Values

Key Type Default Description
nameOverride string "" Partially override resource names (adds suffix)
fullnameOverride string "" Fully override resource names
extraDeploy list [] Array of extra objects to deploy with the release
config.advanced tpl/object {} Advanced configuration options.
config.auth tpl/object {} Auth configuration options.
config.core tpl/object {} Core configuration options.
If external admins in auth are defined and there are no admin_user and admin_password defined here, the default admin account will be disabled.
config.database tpl/object {} Database configuration options
config.mail tpl/object {} Mail configuration options
config.statistics tpl/object {} Statistics configuration options
config.web tpl/object {} Web configuration options.
listening_address will be set automatically from service.web.port. external_url is required to enable ingress and certificate resources.
revisionHistoryLimit string 10 The number of old ReplicaSets to retain to allow rollback.
workloadType string "Deployment" Workload type - Deployment or StatefulSet
strategy object {"type":"RollingUpdate"} Update strategy for the workload Valid values are: RollingUpdate or Recreate for Deployment, RollingUpdate or OnDelete for StatefulSet
image.repository string "ghcr.io/h44z/wg-portal" Image repository
image.pullPolicy string "IfNotPresent" Image pull policy
image.tag string "" Overrides the image tag whose default is the chart appVersion
imagePullSecrets list [] Image pull secrets
podAnnotations tpl/object {} Extra annotations to add to the pod
podLabels object {} Extra labels to add to the pod
podSecurityContext object {} Pod Security Context
securityContext.capabilities.add list ["NET_ADMIN"] Add capabilities to the container
initContainers tpl/list [] Pod init containers
sidecarContainers tpl/list [] Pod sidecar containers
dnsPolicy string "ClusterFirst" Set DNS policy for the pod. Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None.
restartPolicy string "Always" Restart policy for all containers within the pod. Valid values are Always, OnFailure or Never.
hostNetwork string false. Use the host's network namespace.
resources object {} Resources requests and limits
command list [] Overwrite pod command
args list [] Additional pod arguments
env tpl/list [] Additional environment variables
envFrom tpl/list [] Additional environment variables from a secret or configMap
livenessProbe object {} Liveness probe configuration
readinessProbe object {} Readiness probe configuration
startupProbe object {} Startup probe configuration
volumes tpl/list [] Additional volumes
volumeMounts tpl/list [] Additional volumeMounts
nodeSelector object {"kubernetes.io/os":"linux"} Node Selector configuration
tolerations list [] Tolerations configuration
affinity object {} Affinity configuration
service.mixed.enabled bool false Whether to create a single service for the web and wireguard interfaces
service.mixed.type string "LoadBalancer" Service type
service.web.annotations object {} Annotations for the web service
service.web.type string "ClusterIP" Web service type
service.web.port int 8888 Web service port Used for the web interface listener
service.web.appProtocol string "http" Web service appProtocol. Will be auto set to https if certificate is enabled.
service.wireguard.annotations object {} Annotations for the WireGuard service
service.wireguard.type string "LoadBalancer" Wireguard service type
service.wireguard.ports list [51820] Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface.
service.metrics.port int 8787
ingress.enabled bool false Specifies whether an ingress resource should be created
ingress.className string "" Ingress class name
ingress.annotations object {} Ingress annotations
ingress.tls bool false Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret
certificate.enabled bool false Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web.
certificate.issuer.name string "" Certificate issuer name
certificate.issuer.kind string "" Certificate issuer kind (ClusterIssuer or Issuer)
certificate.issuer.group string "cert-manager.io" Certificate issuer group
certificate.duration string "" Optional. Documentation
certificate.renewBefore string "" Optional. Documentation
certificate.commonName string "" Optional. Documentation
certificate.emailAddresses list [] Optional. Documentation
certificate.ipAddresses list [] Optional. Documentation
certificate.keystores object {} Optional. Documentation
certificate.privateKey object {} Optional. Documentation
certificate.secretTemplate object {} Optional. Documentation
certificate.subject object {} Optional. Documentation
certificate.uris list [] Optional. Documentation
certificate.usages list [] Optional. Documentation
persistence.enabled bool false Specifies whether an persistent volume should be created
persistence.annotations object {} Persistent Volume Claim annotations
persistence.storageClass string "" Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used.
persistence.accessMode string "ReadWriteOnce" Persistent Volume Access Mode
persistence.size string "1Gi" Persistent Volume size
persistence.volumeName string "" Persistent Volume Name (optional)
serviceAccount.create bool true Specifies whether a service account should be created
serviceAccount.annotations object {} Service account annotations
serviceAccount.automount bool false Automatically mount a ServiceAccount's API credentials
serviceAccount.name string "" The name of the service account to use. If not set and create is true, a name is generated using the fullname template
monitoring.enabled bool false Enable Prometheus monitoring.
monitoring.apiVersion string "monitoring.coreos.com/v1" API version of the Prometheus resource. Use azmonitoring.coreos.com/v1 for Azure Managed Prometheus.
monitoring.kind string "PodMonitor" Kind of the Prometheus resource. Could be PodMonitor or ServiceMonitor.
monitoring.labels object {} Resource labels.
monitoring.annotations object {} Resource annotations.
monitoring.interval string 1m Interval at which metrics should be scraped. If not specified config.statistics.data_collection_interval interval is used.
monitoring.metricRelabelings list [] Relabelings to samples before ingestion.
monitoring.relabelings list [] Relabelings to samples before scraping.
monitoring.scrapeTimeout string "" Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used.
monitoring.jobLabel string "" The label to use to retrieve the job name from.
monitoring.podTargetLabels object {} Transfers labels on the Kubernetes Pod onto the target.
monitoring.dashboard.enabled bool false Enable Grafana dashboard.
monitoring.dashboard.annotations object {} Annotations for the dashboard ConfigMap.
monitoring.dashboard.labels object {} Additional labels for the dashboard ConfigMap.
monitoring.dashboard.namespace string "" Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap.
\ No newline at end of file + Helm - WireGuard Portal

Helm

Installing the Chart

To install the chart with the release name wg-portal:

helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
+

This command deploy wg-portal on the Kubernetes cluster in the default configuration. The Values section lists the parameters that can be configured during installation.

Values

Key Type Default Description
nameOverride string "" Partially override resource names (adds suffix)
fullnameOverride string "" Fully override resource names
extraDeploy list [] Array of extra objects to deploy with the release
config.advanced tpl/object {} Advanced configuration options.
config.auth tpl/object {} Auth configuration options.
config.core tpl/object {} Core configuration options.
If external admins in auth are defined and there are no admin_user and admin_password defined here, the default admin account will be disabled.
config.database tpl/object {} Database configuration options
config.mail tpl/object {} Mail configuration options
config.statistics tpl/object {} Statistics configuration options
config.web tpl/object {} Web configuration options.
listening_address will be set automatically from service.web.port. external_url is required to enable ingress and certificate resources.
revisionHistoryLimit string 10 The number of old ReplicaSets to retain to allow rollback.
workloadType string "Deployment" Workload type - Deployment or StatefulSet
strategy object {"type":"RollingUpdate"} Update strategy for the workload Valid values are: RollingUpdate or Recreate for Deployment, RollingUpdate or OnDelete for StatefulSet
image.repository string "ghcr.io/h44z/wg-portal" Image repository
image.pullPolicy string "IfNotPresent" Image pull policy
image.tag string "" Overrides the image tag whose default is the chart appVersion
imagePullSecrets list [] Image pull secrets
podAnnotations tpl/object {} Extra annotations to add to the pod
podLabels object {} Extra labels to add to the pod
podSecurityContext object {} Pod Security Context
securityContext.capabilities.add list ["NET_ADMIN"] Add capabilities to the container
initContainers tpl/list [] Pod init containers
sidecarContainers tpl/list [] Pod sidecar containers
dnsPolicy string "ClusterFirst" Set DNS policy for the pod. Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None.
restartPolicy string "Always" Restart policy for all containers within the pod. Valid values are Always, OnFailure or Never.
hostNetwork string false. Use the host's network namespace.
resources object {} Resources requests and limits
command list [] Overwrite pod command
args list [] Additional pod arguments
env tpl/list [] Additional environment variables
envFrom tpl/list [] Additional environment variables from a secret or configMap
livenessProbe object {} Liveness probe configuration
readinessProbe object {} Readiness probe configuration
startupProbe object {} Startup probe configuration
volumes tpl/list [] Additional volumes
volumeMounts tpl/list [] Additional volumeMounts
nodeSelector object {"kubernetes.io/os":"linux"} Node Selector configuration
tolerations list [] Tolerations configuration
affinity object {} Affinity configuration
service.mixed.enabled bool false Whether to create a single service for the web and wireguard interfaces
service.mixed.type string "LoadBalancer" Service type
service.web.annotations object {} Annotations for the web service
service.web.type string "ClusterIP" Web service type
service.web.port int 8888 Web service port Used for the web interface listener
service.web.appProtocol string "http" Web service appProtocol. Will be auto set to https if certificate is enabled.
service.wireguard.annotations object {} Annotations for the WireGuard service
service.wireguard.type string "LoadBalancer" Wireguard service type
service.wireguard.ports list [51820] Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface.
service.metrics.port int 8787
ingress.enabled bool false Specifies whether an ingress resource should be created
ingress.className string "" Ingress class name
ingress.annotations object {} Ingress annotations
ingress.tls bool false Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret
certificate.enabled bool false Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web.
certificate.issuer.name string "" Certificate issuer name
certificate.issuer.kind string "" Certificate issuer kind (ClusterIssuer or Issuer)
certificate.issuer.group string "cert-manager.io" Certificate issuer group
certificate.duration string "" Optional. Documentation
certificate.renewBefore string "" Optional. Documentation
certificate.commonName string "" Optional. Documentation
certificate.emailAddresses list [] Optional. Documentation
certificate.ipAddresses list [] Optional. Documentation
certificate.keystores object {} Optional. Documentation
certificate.privateKey object {} Optional. Documentation
certificate.secretTemplate object {} Optional. Documentation
certificate.subject object {} Optional. Documentation
certificate.uris list [] Optional. Documentation
certificate.usages list [] Optional. Documentation
persistence.enabled bool false Specifies whether an persistent volume should be created
persistence.annotations object {} Persistent Volume Claim annotations
persistence.storageClass string "" Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used.
persistence.accessMode string "ReadWriteOnce" Persistent Volume Access Mode
persistence.size string "1Gi" Persistent Volume size
persistence.volumeName string "" Persistent Volume Name (optional)
serviceAccount.create bool true Specifies whether a service account should be created
serviceAccount.annotations object {} Service account annotations
serviceAccount.automount bool false Automatically mount a ServiceAccount's API credentials
serviceAccount.name string "" The name of the service account to use. If not set and create is true, a name is generated using the fullname template
monitoring.enabled bool false Enable Prometheus monitoring.
monitoring.apiVersion string "monitoring.coreos.com/v1" API version of the Prometheus resource. Use azmonitoring.coreos.com/v1 for Azure Managed Prometheus.
monitoring.kind string "PodMonitor" Kind of the Prometheus resource. Could be PodMonitor or ServiceMonitor.
monitoring.labels object {} Resource labels.
monitoring.annotations object {} Resource annotations.
monitoring.interval string 1m Interval at which metrics should be scraped. If not specified config.statistics.data_collection_interval interval is used.
monitoring.metricRelabelings list [] Relabelings to samples before ingestion.
monitoring.relabelings list [] Relabelings to samples before scraping.
monitoring.scrapeTimeout string "" Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used.
monitoring.jobLabel string "" The label to use to retrieve the job name from.
monitoring.podTargetLabels object {} Transfers labels on the Kubernetes Pod onto the target.
monitoring.dashboard.enabled bool false Enable Grafana dashboard.
monitoring.dashboard.annotations object {} Annotations for the dashboard ConfigMap.
monitoring.dashboard.labels object {} Additional labels for the dashboard ConfigMap.
monitoring.dashboard.namespace string "" Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap.
\ No newline at end of file diff --git a/master/documentation/getting-started/reverse-proxy/index.html b/master/documentation/getting-started/reverse-proxy/index.html index 97fce20..12ba3f8 100644 --- a/master/documentation/getting-started/reverse-proxy/index.html +++ b/master/documentation/getting-started/reverse-proxy/index.html @@ -1,4 +1,4 @@ - Reverse Proxy (HTTPS) - WireGuard Portal

Reverse Proxy (HTTPS)

Reverse Proxy for HTTPS

For production deployments, always serve the WireGuard Portal over HTTPS. You have two options to secure your connection:

Reverse Proxy

Let a front‐end proxy handle HTTPS for you. This also frees you from managing certificates manually and is therefore the preferred option. You can use Nginx, Traefik, Caddy or any other proxy.

Below is an example using a Docker Compose stack with Traefik. It exposes the WireGuard Portal on https://wg.domain.com and redirects initial HTTP traffic to HTTPS.

services:
+ Reverse Proxy (HTTPS) - WireGuard Portal      

Reverse Proxy (HTTPS)

Reverse Proxy for HTTPS

For production deployments, always serve the WireGuard Portal over HTTPS. You have two options to secure your connection:

Reverse Proxy

Let a front‐end proxy handle HTTPS for you. This also frees you from managing certificates manually and is therefore the preferred option. You can use Nginx, Traefik, Caddy or any other proxy.

Below is an example using a Docker Compose stack with Traefik. It exposes the WireGuard Portal on https://wg.domain.com and redirects initial HTTP traffic to HTTPS.

services:
   reverse-proxy:
     image: traefik:v3.3
     restart: unless-stopped
@@ -66,4 +66,4 @@
 

Built-in TLS

If you prefer to let WireGuard Portal handle TLS itself, you can use the built-in TLS support. In your config.yaml, under the web section, point to your certificate and key files:

web:
   cert_file: /path/to/your/fullchain.pem
   key_file:  /path/to/your/privkey.pem
-

The web server will then use these files to serve HTTPS traffic directly instead of HTTP.

\ No newline at end of file +

The web server will then use these files to serve HTTPS traffic directly instead of HTTP.

\ No newline at end of file diff --git a/master/documentation/getting-started/sources/index.html b/master/documentation/getting-started/sources/index.html index 8e38459..9985f28 100644 --- a/master/documentation/getting-started/sources/index.html +++ b/master/documentation/getting-started/sources/index.html @@ -1,8 +1,8 @@ - Sources - WireGuard Portal

Sources

To build the application from source files, use the Makefile provided in the repository.

Requirements

Build

# Get source code
+ Sources - WireGuard Portal      

Sources

To build the application from source files, use the Makefile provided in the repository.

Requirements

Build

# Get source code
 git clone https://github.com/h44z/wg-portal -b ${WG_PORTAL_VERSION:-master} --depth 1
 cd wg-portal
 # Build the frontend
 make frontend
 # Build the backend
 make build
-

Install

Compiled binary will be available in ./dist directory.

For installation instructions, check the Binaries section.

\ No newline at end of file +

Install

Compiled binary will be available in ./dist directory.

For installation instructions, check the Binaries section.

\ No newline at end of file diff --git a/master/documentation/monitoring/prometheus/index.html b/master/documentation/monitoring/prometheus/index.html index 8cb3bf2..557a89e 100644 --- a/master/documentation/monitoring/prometheus/index.html +++ b/master/documentation/monitoring/prometheus/index.html @@ -1,8 +1,8 @@ - Monitoring - WireGuard Portal

By default, WG-Portal exposes Prometheus metrics on port 8787 if interface/peer statistic data collection is enabled.

Exposed Metrics

Metric Type Description
wireguard_interface_received_bytes_total gauge Bytes received through the interface.
wireguard_interface_sent_bytes_total gauge Bytes sent through the interface.
wireguard_peer_last_handshake_seconds gauge Seconds from the last handshake with the peer.
wireguard_peer_received_bytes_total gauge Bytes received from the peer.
wireguard_peer_sent_bytes_total gauge Bytes sent to the peer.
wireguard_peer_up gauge Peer connection state (boolean: 1/0).

Prometheus Config

Add the following scrape job to your Prometheus config file:

# prometheus.yaml
+ Monitoring - WireGuard Portal      

By default, WG-Portal exposes Prometheus metrics on port 8787 if interface/peer statistic data collection is enabled.

Exposed Metrics

Metric Type Description
wireguard_interface_received_bytes_total gauge Bytes received through the interface.
wireguard_interface_sent_bytes_total gauge Bytes sent through the interface.
wireguard_peer_last_handshake_seconds gauge Seconds from the last handshake with the peer.
wireguard_peer_received_bytes_total gauge Bytes received from the peer.
wireguard_peer_sent_bytes_total gauge Bytes sent to the peer.
wireguard_peer_up gauge Peer connection state (boolean: 1/0).

Prometheus Config

Add the following scrape job to your Prometheus config file:

# prometheus.yaml
 scrape_configs:
   - job_name: wg-portal
     scrape_interval: 60s
     static_configs:
       - targets:
           - localhost:8787 # Change localhost to IP Address or hostname with WG-Portal
-

Grafana Dashboard

You may import dashboard.json into your Grafana instance.

Dashboard

\ No newline at end of file +

Grafana Dashboard

You may import dashboard.json into your Grafana instance.

Dashboard

\ No newline at end of file diff --git a/master/documentation/overview/index.html b/master/documentation/overview/index.html index 59cfa34..b22f14c 100644 --- a/master/documentation/overview/index.html +++ b/master/documentation/overview/index.html @@ -1 +1 @@ - Overview - WireGuard Portal

Overview

WireGuard Portal is a simple, web-based configuration portal for WireGuard server management. The portal uses the WireGuard wgctrl library to manage existing VPN interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN connections.

The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.

Features

  • Self-hosted - the whole application is a single binary
  • Responsive multi-language web UI written in Vue.js
  • Automatically selects IP from the network pool assigned to the client
  • QR-Code for convenient mobile client configuration
  • Sends email to the client with QR-code and client config
  • Enable / Disable clients seamlessly
  • Generation of wg-quick configuration file (wgX.conf) if required
  • User authentication (database, OAuth, or LDAP), Passkey support
  • IPv6 ready
  • Docker ready
  • Can be used with existing WireGuard setups
  • Support for multiple WireGuard interfaces
  • Supports multiple WireGuard backends (wgctrl or MikroTik [BETA])
  • Peer Expiry Feature
  • Handles route and DNS settings like wg-quick does
  • Exposes Prometheus metrics for monitoring and alerting
  • REST API for management and client deployment
  • Webhook for custom actions on peer, interface, or user updates
\ No newline at end of file + Overview - WireGuard Portal

Overview

WireGuard Portal is a simple, web-based configuration portal for WireGuard server management. The portal uses the WireGuard wgctrl library to manage existing VPN interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN connections.

The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.

Features

  • Self-hosted - the whole application is a single binary
  • Responsive multi-language web UI with dark-mode written in Vue.js
  • Automatically selects IP from the network pool assigned to the client
  • QR-Code for convenient mobile client configuration
  • Sends email to the client with QR-code and client config
  • Enable / Disable clients seamlessly
  • Generation of wg-quick configuration file (wgX.conf) if required
  • User authentication (database, OAuth, or LDAP), Passkey support
  • IPv6 ready
  • Docker ready
  • Can be used with existing WireGuard setups
  • Support for multiple WireGuard interfaces
  • Supports multiple WireGuard backends (wgctrl or MikroTik)
  • Peer Expiry Feature
  • Handles route and DNS settings like wg-quick does
  • Exposes Prometheus metrics for monitoring and alerting
  • REST API for management and client deployment
  • Webhook for custom actions on peer, interface, or user updates
\ No newline at end of file diff --git a/master/documentation/rest-api/api-doc/index.html b/master/documentation/rest-api/api-doc/index.html index d29a3cc..773a6e2 100644 --- a/master/documentation/rest-api/api-doc/index.html +++ b/master/documentation/rest-api/api-doc/index.html @@ -1,5 +1,5 @@ - REST API - WireGuard Portal

REST API

REST API

Upgrade

Major upgrades between different versions may require special procedures, which are described in the following sections.

Upgrade from v1 to v2

⚠ Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!

To start the upgrade process, start the wg-portal binary with the -migrateFrom parameter. The configuration (config.yaml) for WireGuard Portal must be updated and valid before starting the upgrade.

To upgrade from a previous SQLite database, start wg-portal like:

./wg-portal-amd64 -migrateFrom=old_wg_portal.db
+ Upgrade - WireGuard Portal      

Upgrade

Major upgrades between different versions may require special procedures, which are described in the following sections.

Upgrade from v1 to v2

⚠ Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!

To start the upgrade process, start the wg-portal binary with the -migrateFrom parameter. The configuration (config.yaml) for WireGuard Portal must be updated and valid before starting the upgrade.

To upgrade from a previous SQLite database, start wg-portal like:

./wg-portal-amd64 -migrateFrom=old_wg_portal.db
 

You can also specify the database type using the parameter -migrateFromType. Supported database types: mysql, mssql, postgres or sqlite.

For example:

./wg-portal-amd64 -migrateFromType=mysql -migrateFrom='user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local'
 

The upgrade will transform the old, existing database and store the values in the new database specified in the config.yaml configuration file. Ensure that the new database does not contain any data!

If you are using Docker, you can adapt the docker-compose.yml file to start the upgrade process:

services:
   wg-portal:
@@ -6,4 +6,4 @@
     # ... other settings
     restart: no
     command: ["-migrateFrom=/app/data/old_wg_portal.db"]
-
\ No newline at end of file +
\ No newline at end of file diff --git a/master/documentation/usage/backends/index.html b/master/documentation/usage/backends/index.html index 6e99394..bc6baca 100644 --- a/master/documentation/usage/backends/index.html +++ b/master/documentation/usage/backends/index.html @@ -1,4 +1,4 @@ - Backends - WireGuard Portal

Backends

WireGuard Portal can manage WireGuard interfaces and peers on different backends. Each backend represents a system where interfaces actually live. You can register multiple backends and choose which one to use per interface. A global default backend determines where newly created interfaces go (unless you explicitly choose another in the UI).

Supported backends: - Local (default): Manages interfaces on the host running WireGuard Portal (Linux WireGuard via wgctrl). Use this when the portal should directly configure wg devices on the same server. - MikroTik RouterOS (beta): Manages interfaces and peers on MikroTik devices via the RouterOS REST API. Use this to control WG interfaces on RouterOS v7+.

How backend selection works: - The default backend is configured at backend.default (local or the id of a defined MikroTik backend). New interfaces created in the UI will use this backend by default. - Each interface stores its backend. You can select a different backend when creating a new interface.

Configuring MikroTik backends (RouterOS v7+)

⚠ The MikroTik backend is currently marked beta. While basic functionality is implemented, some advanced features are not yet implemented or contain bugs. Please test carefully before using in production.

The MikroTik backend uses the REST API under a base URL ending with /rest. You can register one or more MikroTik devices as backends for a single WireGuard Portal instance.

Prerequisites on MikroTik:

  • RouterOS v7 with WireGuard support.
  • REST API enabled and reachable over HTTP(S). A typical base URL is https://:8729/rest or https:///rest depending on your service setup.
  • A dedicated RouterOS user with the following group permissions:
  • api (for logging in via REST API)
  • rest-api (for logging in via REST API)
  • read (to read interface and peer data)
  • write (to create/update interfaces and peers)
  • test (to perform ping checks)
  • sensitive (to read private keys)
  • TLS certificate on the device is recommended. If you use a self-signed certificate during testing, set api_verify_tls: false in wg-portal (not recommended for production).

Example WireGuard Portal configuration (config/config.yaml):

backend:
+ Backends - WireGuard Portal      

Backends

WireGuard Portal can manage WireGuard interfaces and peers on different backends. Each backend represents a system where interfaces actually live. You can register multiple backends and choose which one to use per interface. A global default backend determines where newly created interfaces go (unless you explicitly choose another in the UI).

Supported backends: - Local (default): Manages interfaces on the host running WireGuard Portal (Linux WireGuard via wgctrl). Use this when the portal should directly configure wg devices on the same server. - MikroTik RouterOS (beta): Manages interfaces and peers on MikroTik devices via the RouterOS REST API. Use this to control WG interfaces on RouterOS v7+.

How backend selection works: - The default backend is configured at backend.default (local or the id of a defined MikroTik backend). New interfaces created in the UI will use this backend by default. - Each interface stores its backend. You can select a different backend when creating a new interface.

Configuring MikroTik backends (RouterOS v7+)

⚠ The MikroTik backend is currently marked beta. While basic functionality is implemented, some advanced features are not yet implemented or contain bugs. Please test carefully before using in production.

The MikroTik backend uses the REST API under a base URL ending with /rest. You can register one or more MikroTik devices as backends for a single WireGuard Portal instance.

Prerequisites on MikroTik:

  • RouterOS v7 with WireGuard support.
  • REST API enabled and reachable over HTTP(S). A typical base URL is https://:8729/rest or https:///rest depending on your service setup.
  • A dedicated RouterOS user with the following group permissions:
  • api (for logging in via REST API)
  • rest-api (for logging in via REST API)
  • read (to read interface and peer data)
  • write (to create/update interfaces and peers)
  • test (to perform ping checks)
  • sensitive (to read private keys)
  • TLS certificate on the device is recommended. If you use a self-signed certificate during testing, set api_verify_tls: false in wg-portal (not recommended for production).

Example WireGuard Portal configuration (config/config.yaml):

backend:
   # default backend decides where new interfaces are created
   default: mikrotik-prod
 
@@ -12,4 +12,4 @@
       api_timeout: 30s             # maximum request duration
       concurrency: 5               # limit parallel REST calls to device
       debug: false                 # verbose logging for this backend
-

Known limitations:

  • The MikroTik backend is still in beta. Some features may not work as expected.
  • Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks)
\ No newline at end of file +

Known limitations:

  • The MikroTik backend is still in beta. Some features may not work as expected.
  • Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks)
\ No newline at end of file diff --git a/master/documentation/usage/general/index.html b/master/documentation/usage/general/index.html index bb9825e..f8834e6 100644 --- a/master/documentation/usage/general/index.html +++ b/master/documentation/usage/general/index.html @@ -1,2 +1,2 @@ - General - WireGuard Portal

General

This documentation section describes the general usage of WireGuard Portal. If you are looking for specific setup instructions, please refer to the Getting Started and Configuration sections, for example, using a Docker deployment.

Basic Concepts

WireGuard Portal is a web-based configuration portal for WireGuard server management. It allows managing multiple WireGuard interfaces and users from a single web UI. WireGuard Interfaces can be categorized into three types:

  • Server: A WireGuard server interface that to which multiple peers can connect. In this mode, it is possible to specify default settings for all peers, such as the IP address range, DNS servers, and MTU size.
  • Client: A WireGuard client interface that can be used to connect to a WireGuard server. Usually, such an interface has exactly one peer.
  • Unknown: This is the default type for imported interfaces. It is encouraged to change the type to either Server or Client after importing the interface.

Accessing the Web UI

The web UI should be accessed via the URL specified in the external_url property of the configuration file. By default, WireGuard Portal listens on port 8888 for HTTP connections. Check the Security section for more information on securing the web UI.

So the default URL to access the web UI is:

http://localhost:8888
-

A freshly set-up WireGuard Portal instance will have a default admin user with the username admin@wgportal.local and the password wgportal-default. You can and should override the default credentials in the configuration file. Make sure to change the default password immediately after the first login!

Basic UI Description

WireGuard Portal Web UI

As seen in the screenshot above, the web UI is divided into several sections which are accessible via the navigation bar on the top of the screen.

  1. Home: The landing page of WireGuard Portal. It provides a staring point for the user to access the different sections of the web UI. It also provides quick links to WireGuard Client downloads or official documentation.
  2. Interfaces: This section allows you to manage the WireGuard interfaces. You can add, edit, or delete interfaces, as well as view their status and statistics. Peers for each interface can be managed here as well.
  3. Users: This section allows you to manage the users of WireGuard Portal. You can add, edit, or delete users, as well as view their status and statistics.
  4. Key Generator: This section allows you to generate WireGuard keys locally on your browser. The generated keys are never sent to the server. This is useful if you want to generate keys for a new peer without having to store the private keys in the database.
  5. Profile / Settings: This section allows you to access your own profile page, settings, and audit logs.

Interface View

WireGuard Portal Interface View

The interface view provides an overview of the WireGuard interfaces and peers configured in WireGuard Portal.

The most important elements are:

  1. Interface Selector: This dropdown allows you to select the WireGuard interface you want to manage. All further actions will be performed on the selected interface.
  2. Create new Interface: This button allows you to create a new WireGuard interface.
  3. Interface Overview: This section provides an overview of the selected WireGuard interface. It shows the interface type, number of peers, and other important information.
  4. List of Peers: This section provides a list of all peers associated with the selected WireGuard interface. You can view, add, edit, or delete peers from this list.
  5. Add new Peer: This button allows you to add a new peer to the selected WireGuard interface.
  6. Add multiple Peers: This button allows you to add multiple peers to the selected WireGuard interface. This is useful if you want to add a large number of peers at once.
\ No newline at end of file + General - WireGuard Portal

General

This documentation section describes the general usage of WireGuard Portal. If you are looking for specific setup instructions, please refer to the Getting Started and Configuration sections, for example, using a Docker deployment.

Basic Concepts

WireGuard Portal is a web-based configuration portal for WireGuard server management. It allows managing multiple WireGuard interfaces and users from a single web UI. WireGuard Interfaces can be categorized into three types:

  • Server: A WireGuard server interface that to which multiple peers can connect. In this mode, it is possible to specify default settings for all peers, such as the IP address range, DNS servers, and MTU size.
  • Client: A WireGuard client interface that can be used to connect to a WireGuard server. Usually, such an interface has exactly one peer.
  • Unknown: This is the default type for imported interfaces. It is encouraged to change the type to either Server or Client after importing the interface.

Accessing the Web UI

The web UI should be accessed via the URL specified in the external_url property of the configuration file. By default, WireGuard Portal listens on port 8888 for HTTP connections. Check the Security section for more information on securing the web UI.

So the default URL to access the web UI is:

http://localhost:8888
+

A freshly set-up WireGuard Portal instance will have a default admin user with the username admin@wgportal.local and the password wgportal-default. You can and should override the default credentials in the configuration file. Make sure to change the default password immediately after the first login!

Basic UI Description

WireGuard Portal Web UI

As seen in the screenshot above, the web UI is divided into several sections which are accessible via the navigation bar on the top of the screen.

  1. Home: The landing page of WireGuard Portal. It provides a staring point for the user to access the different sections of the web UI. It also provides quick links to WireGuard Client downloads or official documentation.
  2. Interfaces: This section allows you to manage the WireGuard interfaces. You can add, edit, or delete interfaces, as well as view their status and statistics. Peers for each interface can be managed here as well.
  3. Users: This section allows you to manage the users of WireGuard Portal. You can add, edit, or delete users, as well as view their status and statistics.
  4. Key Generator: This section allows you to generate WireGuard keys locally on your browser. The generated keys are never sent to the server. This is useful if you want to generate keys for a new peer without having to store the private keys in the database.
  5. Profile / Settings: This section allows you to access your own profile page, settings, and audit logs.

Interface View

WireGuard Portal Interface View

The interface view provides an overview of the WireGuard interfaces and peers configured in WireGuard Portal.

The most important elements are:

  1. Interface Selector: This dropdown allows you to select the WireGuard interface you want to manage. All further actions will be performed on the selected interface.
  2. Create new Interface: This button allows you to create a new WireGuard interface.
  3. Interface Overview: This section provides an overview of the selected WireGuard interface. It shows the interface type, number of peers, and other important information.
  4. List of Peers: This section provides a list of all peers associated with the selected WireGuard interface. You can view, add, edit, or delete peers from this list.
  5. Add new Peer: This button allows you to add a new peer to the selected WireGuard interface.
  6. Add multiple Peers: This button allows you to add multiple peers to the selected WireGuard interface. This is useful if you want to add a large number of peers at once.
\ No newline at end of file diff --git a/master/documentation/usage/ldap/index.html b/master/documentation/usage/ldap/index.html index 63af4e9..ea702fb 100644 --- a/master/documentation/usage/ldap/index.html +++ b/master/documentation/usage/ldap/index.html @@ -1,6 +1,6 @@ - LDAP - WireGuard Portal

LDAP

WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. You can even register multiple LDAP servers side-by-side. When someone logs in via LDAP, their specific provider is remembered, so there's no risk of cross-provider conflicts. Details on the log-in process can be found in the Security documentation.

If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. The synchronization process can be fine-tuned by multiple parameters, which are described below.

LDAP Synchronization

WireGuard Portal can automatically synchronize users from LDAP to the database. To enable this feature, set the sync_interval property in the LDAP provider configuration to a value greater than "0". The value is a string representing a duration, such as "15m" for 15 minutes or "1h" for 1 hour (check the exact format definition for details). The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. Also make sure that the sync_filter property is a well-formed LDAP filter, or synchronization will fail.

Limiting Synchronization to Specific Users

Use the sync_filter property in your LDAP provider block to restrict which users get synchronized. It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database.

For example, to import only users with a mail attribute:

auth:
+ LDAP - WireGuard Portal      

LDAP

WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. You can even register multiple LDAP servers side-by-side. When someone logs in via LDAP, their specific provider is remembered, so there's no risk of cross-provider conflicts. Details on the log-in process can be found in the Security documentation.

If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. The synchronization process can be fine-tuned by multiple parameters, which are described below.

LDAP Synchronization

WireGuard Portal can automatically synchronize users from LDAP to the database. To enable this feature, set the sync_interval property in the LDAP provider configuration to a value greater than "0". The value is a string representing a duration, such as "15m" for 15 minutes or "1h" for 1 hour (check the exact format definition for details). The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. Also make sure that the sync_filter property is a well-formed LDAP filter, or synchronization will fail.

Limiting Synchronization to Specific Users

Use the sync_filter property in your LDAP provider block to restrict which users get synchronized. It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database.

For example, to import only users with a mail attribute:

auth:
   ldap:
     - id: ldap
       # ... other settings
       sync_filter: (mail=*)
-

Disable Missing Users

If you set the disable_missing property to true, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. All peers associated with that user will also be disabled.

If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the auto_re_enable property to true. This will only re-enable the user if they where disabled by the synchronization process. Manually disabled users will not be re-enabled.

\ No newline at end of file +

Disable Missing Users

If you set the disable_missing property to true, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. All peers associated with that user will also be disabled.

If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the auto_re_enable property to true. This will only re-enable the user if they where disabled by the synchronization process. Manually disabled users will not be re-enabled.

\ No newline at end of file diff --git a/master/documentation/usage/security/index.html b/master/documentation/usage/security/index.html index c797ff1..a503e34 100644 --- a/master/documentation/usage/security/index.html +++ b/master/documentation/usage/security/index.html @@ -1,4 +1,4 @@ - Security - WireGuard Portal

Security

This section describes the security features available to administrators for hardening WireGuard Portal and protecting its data.

Authentication

WireGuard Portal supports multiple authentication methods, including:

  • Local user accounts
  • LDAP authentication
  • OAuth and OIDC authentication
  • Passkey authentication (WebAuthn)

Users can have two roles which limit their permissions in WireGuard Portal:

  • User: Can manage their own account and peers.
  • Admin: Can manage all users and peers, including the ability to manage WireGuard interfaces.

Password Security

WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. Local users are stored in the database, while LDAP users are authenticated against an external LDAP server.

On initial startup, WireGuard Portal automatically creates a local admin account with the password wgportal-default.

⚠ This password must be changed immediately after the first login.

The minimum password length for all local users can be configured in the auth section of the configuration file. The default value is 16 characters, see min_password_length. The minimum password length is also enforced for the default admin user.

Passkey (WebAuthn) Authentication

Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. This feature is enabled by default and can be configured in the webauthn section of the configuration file.

Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked.

⚠ Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback).

To register a Passkey, open the settings page (1) in the web UI and click on the "Register Passkey" (2) button.

Passkey UI

OAuth and OIDC Authentication

WireGuard Portal supports OAuth and OIDC authentication. You can use any OAuth or OIDC provider that supports the authorization code flow, such as Google, GitHub, or Keycloak.

For OAuth or OIDC to work, you need to configure the external_url property in the web section of the configuration file. If you are planning to expose the portal to the internet, make sure that the external_url is configured to use HTTPS.

To add OIDC or OAuth authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth provider and configure a new authentication provider in the auth section of the configuration file. Make sure that each configured provider has a unique provider_name property set. Samples can be seen here.

Limiting Login to Specific Domains

You can limit the login to specific domains by setting the allowed_domains property for OAuth or OIDC providers. This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. For example, if you want to allow only users with an email address ending in outlook.com to log in, set the property as follows:

auth:
+ Security - WireGuard Portal      

Security

This section describes the security features available to administrators for hardening WireGuard Portal and protecting its data.

Authentication

WireGuard Portal supports multiple authentication methods, including:

  • Local user accounts
  • LDAP authentication
  • OAuth and OIDC authentication
  • Passkey authentication (WebAuthn)

Users can have two roles which limit their permissions in WireGuard Portal:

  • User: Can manage their own account and peers.
  • Admin: Can manage all users and peers, including the ability to manage WireGuard interfaces.

Password Security

WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. Local users are stored in the database, while LDAP users are authenticated against an external LDAP server.

On initial startup, WireGuard Portal automatically creates a local admin account with the password wgportal-default.

⚠ This password must be changed immediately after the first login.

The minimum password length for all local users can be configured in the auth section of the configuration file. The default value is 16 characters, see min_password_length. The minimum password length is also enforced for the default admin user.

Passkey (WebAuthn) Authentication

Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. This feature is enabled by default and can be configured in the webauthn section of the configuration file.

Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked.

⚠ Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback).

To register a Passkey, open the settings page (1) in the web UI and click on the "Register Passkey" (2) button.

Passkey UI

OAuth and OIDC Authentication

WireGuard Portal supports OAuth and OIDC authentication. You can use any OAuth or OIDC provider that supports the authorization code flow, such as Google, GitHub, or Keycloak.

For OAuth or OIDC to work, you need to configure the external_url property in the web section of the configuration file. If you are planning to expose the portal to the internet, make sure that the external_url is configured to use HTTPS.

To add OIDC or OAuth authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth provider and configure a new authentication provider in the auth section of the configuration file. Make sure that each configured provider has a unique provider_name property set. Samples can be seen here.

Limiting Login to Specific Domains

You can limit the login to specific domains by setting the allowed_domains property for OAuth or OIDC providers. This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. For example, if you want to allow only users with an email address ending in outlook.com to log in, set the property as follows:

auth:
   oidc:
     - provider_name: "oidc1"
       # ... other settings
@@ -25,4 +25,4 @@
     - provider_name: "ldap1"
       # ... other settings
       login_filter: "(&(objectClass=organizationalPerson)(uid={{login_identifier}}))"
-

The login_filter should always be designed to return at most one user.

Limit Login to Existing Users

You can limit the login to existing users only by setting the registration_enabled property to false for LDAP providers. If registration is enabled, new users will be created in the database when they log in for the first time.

Admin Mapping

You can map users to admin roles based on their group membership in the LDAP server. To do this, set the admin_group and memberof property for the provider. The admin_group property defines the distinguished name of the group that is allowed to log in as admin. All groups that are listed in the memberof attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access.

UI and API Access

WireGuard Portal provides a web UI and a REST API for user interaction. It is important to secure these interfaces to prevent unauthorized access and data breaches.

HTTPS

It is recommended to use HTTPS for all communication with the portal to prevent eavesdropping.

Event though, WireGuard Portal supports HTTPS out of the box, it is recommended to use a reverse proxy like Nginx or Traefik to handle SSL termination and other security features. A detailed explanation is available in the Reverse Proxy section.

\ No newline at end of file +

The login_filter should always be designed to return at most one user.

Limit Login to Existing Users

You can limit the login to existing users only by setting the registration_enabled property to false for LDAP providers. If registration is enabled, new users will be created in the database when they log in for the first time.

Admin Mapping

You can map users to admin roles based on their group membership in the LDAP server. To do this, set the admin_group and memberof property for the provider. The admin_group property defines the distinguished name of the group that is allowed to log in as admin. All groups that are listed in the memberof attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access.

UI and API Access

WireGuard Portal provides a web UI and a REST API for user interaction. It is important to secure these interfaces to prevent unauthorized access and data breaches.

HTTPS

It is recommended to use HTTPS for all communication with the portal to prevent eavesdropping.

Event though, WireGuard Portal supports HTTPS out of the box, it is recommended to use a reverse proxy like Nginx or Traefik to handle SSL termination and other security features. A detailed explanation is available in the Reverse Proxy section.

\ No newline at end of file diff --git a/master/documentation/usage/webhooks/index.html b/master/documentation/usage/webhooks/index.html index 3ef9a7b..e85c84f 100644 --- a/master/documentation/usage/webhooks/index.html +++ b/master/documentation/usage/webhooks/index.html @@ -1,4 +1,4 @@ - Webhooks - WireGuard Portal

Webhooks

Webhooks allow WireGuard Portal to notify external services about events such as user creation, device changes, or configuration updates. This enables integration with other systems and automation workflows.

When webhooks are configured and a specified event occurs, WireGuard Portal sends an HTTP POST request to the configured webhook URL. The payload contains event-specific data in JSON format.

Configuration

All available configuration options for webhooks can be found in the configuration overview.

A basic webhook configuration looks like this:

webhook:
+ Webhooks - WireGuard Portal      

Webhooks

Webhooks allow WireGuard Portal to notify external services about events such as user creation, device changes, or configuration updates. This enables integration with other systems and automation workflows.

When webhooks are configured and a specified event occurs, WireGuard Portal sends an HTTP POST request to the configured webhook URL. The payload contains event-specific data in JSON format.

Configuration

All available configuration options for webhooks can be found in the configuration overview.

A basic webhook configuration looks like this:

webhook:
   url: https://your-service.example.com/webhook
 

Security

Webhooks can be secured by using a shared secret. This secret is included in the Authorization header of the webhook request, allowing your service to verify the authenticity of the request. You can set the shared secret in the webhook configuration:

webhook:
   url: https://your-service.example.com/webhook
@@ -90,4 +90,4 @@
     "Mtu": 1420
   }
 }
-
\ No newline at end of file +
\ No newline at end of file diff --git a/master/index.html b/master/index.html index d7a9093..22c92ce 100644 --- a/master/index.html +++ b/master/index.html @@ -1,4 +1,4 @@ - WireGuard Portal - WireGuard Portal

A beautiful and simple UI to manage your WireGuard peers and interfaces

WireGuard Portal is an open source web-based user interface that makes it easy to setup and manage WireGuard VPN connections. It's built on top of WireGuard's official wgctrl library.

Get started

More information about WireGuard

WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.

WireGuard uses state-of-the-art cryptography and still manages to be as easy to configure and deploy as SSH. A combination of extremely high-speed cryptographic primitives and the fact that WireGuard lives inside the Linux kernel means that secure networking can be very high-speed. It is suitable for both small embedded devices like smartphones and fully loaded backbone routers.

\ No newline at end of file +.before, +.after { + margin: 0; +} + +.after figcaption { + background: #fff; + font-weight: bold; + border: 1px solid #c0c0c0; + color: #000000; + opacity: 0.9; + padding: 9px; + position: absolute; + top: 100%; + transform: translateY(-100%); + line-height: 100%; +} + +.before figcaption { + background: #000; + font-weight: bold; + border: 1px solid #c0c0c0; + color: #ffffff; + opacity: 0.9; + padding: 9px; + position: absolute; + top: 100%; + transform: translateY(-100%); + line-height: 100%; +} + +.before figcaption { + left: 0px; +} +.after figcaption { + right: 0px; +} +.custom-animated-handle { + transition: transform 0.2s; +} + +.slider-with-animated-handle:hover .custom-animated-handle { + transform: scale(1.2); +} +.md-typeset img-comparison-slider figure { + margin: initial; +} + +.first-overlay { + color: #000; +} + + +

A beautiful and simple UI to manage your WireGuard peers and interfaces

WireGuard Portal is an open source web-based user interface that makes it easy to setup and manage WireGuard VPN connections. It's built on top of WireGuard's official wgctrl library.

Get started
Light Mode
Light Mode
Dark Mode
Dark Mode

More information about WireGuard

WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.

WireGuard uses state-of-the-art cryptography and still manages to be as easy to configure and deploy as SSH. A combination of extremely high-speed cryptographic primitives and the fact that WireGuard lives inside the Linux kernel means that secure networking can be very high-speed. It is suitable for both small embedded devices like smartphones and fully loaded backbone routers.

\ No newline at end of file diff --git a/master/javascript/img-comparison-slider.js b/master/javascript/img-comparison-slider.js new file mode 100644 index 0000000..2655b62 --- /dev/null +++ b/master/javascript/img-comparison-slider.js @@ -0,0 +1,2 @@ +(()=>{"use strict";var t={792:(t,e,i)=>{i.d(e,{Z:()=>n});var s=i(609),o=i.n(s)()((function(t){return t[1]}));o.push([t.id,':host{--divider-width: 1px;--divider-color: #fff;--divider-shadow: none;--default-handle-width: 50px;--default-handle-color: #fff;--default-handle-opacity: 1;--default-handle-shadow: none;--handle-position-start: 50%;position:relative;display:inline-block;overflow:hidden;line-height:0;direction:ltr}@media screen and (-webkit-min-device-pixel-ratio: 0)and (min-resolution: 0.001dpcm){:host{outline-offset:1px}}::slotted(*){-webkit-user-drag:none;-khtml-user-drag:none;-moz-user-drag:none;-o-user-drag:none;user-drag:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.first{position:absolute;left:0;top:0;right:0;line-height:normal;font-size:100%;max-height:100%;height:100%;width:100%;--exposure: 50%;--keyboard-transition-time: 0ms;--default-transition-time: 0ms;--transition-time: var(--default-transition-time)}.first .first-overlay-container{position:relative;clip-path:inset(0 var(--exposure) 0 0);transition:clip-path var(--transition-time);height:100%}.first .first-overlay{overflow:hidden;height:100%}.first.focused{will-change:clip-path}.first.focused .first-overlay-container{will-change:clip-path}.second{position:relative}.handle-container{transform:translateX(50%);position:absolute;top:0;right:var(--exposure);height:100%;transition:right var(--transition-time),bottom var(--transition-time)}.focused .handle-container{will-change:right}.divider{position:absolute;height:100%;width:100%;left:0;top:0;display:flex;align-items:center;justify-content:center;flex-direction:column}.divider:after{content:" ";display:block;height:100%;border-left-width:var(--divider-width);border-left-style:solid;border-left-color:var(--divider-color);box-shadow:var(--divider-shadow)}.handle{position:absolute;top:var(--handle-position-start);pointer-events:none;box-sizing:border-box;margin-left:1px;transform:translate(calc(-50% - 0.5px), -50%);line-height:0}.default-handle{width:var(--default-handle-width);opacity:var(--default-handle-opacity);transition:all 1s;filter:drop-shadow(var(--default-handle-shadow))}.default-handle path{stroke:var(--default-handle-color)}.vertical .first-overlay-container{clip-path:inset(0 0 var(--exposure) 0)}.vertical .handle-container{transform:translateY(50%);height:auto;top:unset;bottom:var(--exposure);width:100%;left:0;flex-direction:row}.vertical .divider:after{height:1px;width:100%;border-top-width:var(--divider-width);border-top-style:solid;border-top-color:var(--divider-color);border-left:0}.vertical .handle{top:auto;left:var(--handle-position-start);transform:translate(calc(-50% - 0.5px), -50%) rotate(90deg)}',""]);const n=o},609:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var i=t(e);return e[2]?"@media ".concat(e[2]," {").concat(i,"}"):i})).join("")},e.i=function(t,i,s){"string"==typeof t&&(t=[[null,t,""]]);var o={};if(s)for(var n=0;n{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{var t=i(792);const e="rendered",s=(t,e)=>{const i=t.getBoundingClientRect();let s,o;return"mousedown"===e.type?(s=e.clientX,o=e.clientY):(s=e.touches[0].clientX,o=e.touches[0].clientY),s>=i.x&&s<=i.x+i.width&&o>=i.y&&o<=i.y+i.height};let o;const n={ArrowLeft:-1,ArrowRight:1},r=["horizontal","vertical"],a=t=>({x:t.touches[0].pageX,y:t.touches[0].pageY}),d=t=>({x:t.pageX,y:t.pageY}),h="undefined"!=typeof window&&(null===window||void 0===window?void 0:window.HTMLElement);"undefined"!=typeof window&&(window.document&&(o=document.createElement("template"),o.innerHTML='
'),window.customElements.define("img-comparison-slider",class extends h{constructor(){super(),this.exposure=this.hasAttribute("value")?parseFloat(this.getAttribute("value")):50,this.slideOnHover=!1,this.slideDirection="horizontal",this.keyboard="enabled",this.isMouseDown=!1,this.animationDirection=0,this.isFocused=!1,this.dragByHandle=!1,this.onMouseMove=t=>{if(this.isMouseDown||this.slideOnHover){const e=d(t);this.slideToPage(e)}},this.bodyUserSelectStyle="",this.bodyWebkitUserSelectStyle="",this.onMouseDown=t=>{if(this.slideOnHover)return;if(this.handle&&!s(this.handleElement,t))return;t.preventDefault(),window.addEventListener("mousemove",this.onMouseMove),window.addEventListener("mouseup",this.onWindowMouseUp),this.isMouseDown=!0,this.enableTransition();const e=d(t);this.slideToPage(e),this.focus(),this.bodyUserSelectStyle=window.document.body.style.userSelect,this.bodyWebkitUserSelectStyle=window.document.body.style.webkitUserSelect,window.document.body.style.userSelect="none",window.document.body.style.webkitUserSelect="none"},this.onWindowMouseUp=()=>{this.isMouseDown=!1,window.document.body.style.userSelect=this.bodyUserSelectStyle,window.document.body.style.webkitUserSelect=this.bodyWebkitUserSelectStyle,window.removeEventListener("mousemove",this.onMouseMove),window.removeEventListener("mouseup",this.onWindowMouseUp)},this.touchStartPoint=null,this.isTouchComparing=!1,this.hasTouchMoved=!1,this.onTouchStart=t=>{this.dragByHandle&&!s(this.handleElement,t)||(this.touchStartPoint=a(t),this.isFocused&&(this.enableTransition(),this.slideToPage(this.touchStartPoint)))},this.onTouchMove=t=>{if(null===this.touchStartPoint)return;const e=a(t);if(this.isTouchComparing)return this.slideToPage(e),t.preventDefault(),!1;if(!this.hasTouchMoved){const i=Math.abs(e.y-this.touchStartPoint.y),s=Math.abs(e.x-this.touchStartPoint.x);if("horizontal"===this.slideDirection&&is)return this.isTouchComparing=!0,this.focus(),this.slideToPage(e),t.preventDefault(),!1;this.hasTouchMoved=!0}},this.onTouchEnd=()=>{this.isTouchComparing=!1,this.hasTouchMoved=!1,this.touchStartPoint=null},this.onBlur=()=>{this.stopSlideAnimation(),this.isFocused=!1,this.firstElement.classList.remove("focused")},this.onFocus=()=>{this.isFocused=!0,this.firstElement.classList.add("focused")},this.onKeyDown=t=>{if("disabled"===this.keyboard)return;const e=n[t.key];this.animationDirection!==e&&void 0!==e&&(this.animationDirection=e,this.startSlideAnimation())},this.onKeyUp=t=>{if("disabled"===this.keyboard)return;const e=n[t.key];void 0!==e&&this.animationDirection===e&&this.stopSlideAnimation()},this.resetDimensions=()=>{this.imageWidth=this.offsetWidth,this.imageHeight=this.offsetHeight};const e=this.attachShadow({mode:"open"}),i=document.createElement("style");i.innerHTML=t.Z,this.getAttribute("nonce")&&i.setAttribute("nonce",this.getAttribute("nonce")),e.appendChild(i),e.appendChild(o.content.cloneNode(!0)),this.firstElement=e.getElementById("first"),this.handleElement=e.getElementById("handle")}set handle(t){this.dragByHandle="false"!==t.toString().toLowerCase()}get handle(){return this.dragByHandle}get value(){return this.exposure}set value(t){const e=parseFloat(t);e!==this.exposure&&(this.exposure=e,this.enableTransition(),this.setExposure())}get hover(){return this.slideOnHover}set hover(t){this.slideOnHover="false"!==t.toString().toLowerCase(),this.removeEventListener("mousemove",this.onMouseMove),this.slideOnHover&&this.addEventListener("mousemove",this.onMouseMove)}get direction(){return this.slideDirection}set direction(t){this.slideDirection=t.toString().toLowerCase(),this.slide(0),this.firstElement.classList.remove(...r),r.includes(this.slideDirection)&&this.firstElement.classList.add(this.slideDirection)}static get observedAttributes(){return["hover","direction"]}connectedCallback(){this.hasAttribute("tabindex")||(this.tabIndex=0),this.addEventListener("dragstart",(t=>(t.preventDefault(),!1))),new ResizeObserver(this.resetDimensions).observe(this),this.setExposure(0),this.keyboard=this.hasAttribute("keyboard")&&"disabled"===this.getAttribute("keyboard")?"disabled":"enabled",this.addEventListener("keydown",this.onKeyDown),this.addEventListener("keyup",this.onKeyUp),this.addEventListener("focus",this.onFocus),this.addEventListener("blur",this.onBlur),this.addEventListener("touchstart",this.onTouchStart,{passive:!0}),this.addEventListener("touchmove",this.onTouchMove,{passive:!1}),this.addEventListener("touchend",this.onTouchEnd),this.addEventListener("mousedown",this.onMouseDown),this.handle=this.hasAttribute("handle")?this.getAttribute("handle"):this.dragByHandle,this.hover=this.hasAttribute("hover")?this.getAttribute("hover"):this.slideOnHover,this.direction=this.hasAttribute("direction")?this.getAttribute("direction"):this.slideDirection,this.resetDimensions(),this.classList.contains(e)||this.classList.add(e)}disconnectedCallback(){this.transitionTimer&&window.clearTimeout(this.transitionTimer)}attributeChangedCallback(t,e,i){"hover"===t&&(this.hover=i),"direction"===t&&(this.direction=i),"keyboard"===t&&(this.keyboard="disabled"===i?"disabled":"enabled")}setExposure(t=0){var e;this.exposure=(100,(e=this.exposure+t)<0?0:e>100?100:e),this.firstElement.style.setProperty("--exposure",100-this.exposure+"%")}slide(t=0){this.setExposure(t);const e=new Event("slide");this.dispatchEvent(e)}slideToPage(t){"horizontal"===this.slideDirection&&this.slideToPageX(t.x),"vertical"===this.slideDirection&&this.slideToPageY(t.y)}slideToPageX(t){const e=t-this.getBoundingClientRect().left-window.scrollX;this.exposure=e/this.imageWidth*100,this.slide(0)}slideToPageY(t){const e=t-this.getBoundingClientRect().top-window.scrollY;this.exposure=e/this.imageHeight*100,this.slide(0)}enableTransition(){this.firstElement.style.setProperty("--transition-time","100ms"),this.transitionTimer=window.setTimeout((()=>{this.firstElement.style.setProperty("--transition-time","var(--default-transition-time)"),this.transitionTimer=null}),100)}startSlideAnimation(){let t=null,e=this.animationDirection;this.firstElement.style.setProperty("--transition-time","var(--keyboard-transition-time)");const i=s=>{if(0===this.animationDirection||e!==this.animationDirection)return;null===t&&(t=s);const o=(s-t)/16.666666666666668*this.animationDirection;this.slide(o),setTimeout((()=>window.requestAnimationFrame(i)),0),t=s};window.requestAnimationFrame(i)}stopSlideAnimation(){this.animationDirection=0,this.firstElement.style.setProperty("--transition-time","var(--default-transition-time)")}}))})()})(); +//# sourceMappingURL=img-comparison-slider.js.map \ No newline at end of file diff --git a/master/javascript/img-comparison-slider.js.map b/master/javascript/img-comparison-slider.js.map new file mode 100644 index 0000000..d6cfc6f --- /dev/null +++ b/master/javascript/img-comparison-slider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","mappings":"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","sources":["webpack://img-comparison-slider/./src/styles.scss","webpack://img-comparison-slider/../../node_modules/css-loader/dist/runtime/api.js","webpack://img-comparison-slider/webpack/bootstrap","webpack://img-comparison-slider/webpack/runtime/compat get default export","webpack://img-comparison-slider/webpack/runtime/define property getters","webpack://img-comparison-slider/webpack/runtime/hasOwnProperty shorthand","webpack://img-comparison-slider/./src/template.html","webpack://img-comparison-slider/./src/defaults.ts","webpack://img-comparison-slider/./src/isElementAffected.ts","webpack://img-comparison-slider/./src/index.ts","webpack://img-comparison-slider/./src/inBetween.ts"],"sourcesContent":["// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \":host{--divider-width: 1px;--divider-color: #fff;--divider-shadow: none;--default-handle-width: 50px;--default-handle-color: #fff;--default-handle-opacity: 1;--default-handle-shadow: none;--handle-position-start: 50%;position:relative;display:inline-block;overflow:hidden;line-height:0;direction:ltr}@media screen and (-webkit-min-device-pixel-ratio: 0)and (min-resolution: 0.001dpcm){:host{outline-offset:1px}}:host(:focus){outline:2px solid -webkit-focus-ring-color}::slotted(*){-webkit-user-drag:none;-khtml-user-drag:none;-moz-user-drag:none;-o-user-drag:none;user-drag:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.first{position:absolute;left:0;top:0;right:0;line-height:normal;font-size:100%;max-height:100%;height:100%;width:100%;--exposure: 50%;--keyboard-transition-time: 0ms;--default-transition-time: 0ms;--transition-time: var(--default-transition-time)}.first .first-overlay-container{position:relative;clip-path:inset(0 var(--exposure) 0 0);transition:clip-path var(--transition-time);height:100%}.first .first-overlay{overflow:hidden;height:100%}.first.focused{will-change:clip-path}.first.focused .first-overlay-container{will-change:clip-path}.second{position:relative}.handle-container{transform:translateX(50%);position:absolute;top:0;right:var(--exposure);height:100%;transition:right var(--transition-time),bottom var(--transition-time)}.focused .handle-container{will-change:right}.divider{position:absolute;height:100%;width:100%;left:0;top:0;display:flex;align-items:center;justify-content:center;flex-direction:column}.divider:after{content:\\\" \\\";display:block;height:100%;border-left-width:var(--divider-width);border-left-style:solid;border-left-color:var(--divider-color);box-shadow:var(--divider-shadow)}.handle{position:absolute;top:var(--handle-position-start);pointer-events:none;box-sizing:border-box;margin-left:1px;transform:translate(calc(-50% - 0.5px), -50%);line-height:0}.default-handle{width:var(--default-handle-width);opacity:var(--default-handle-opacity);transition:all 1s;filter:drop-shadow(var(--default-handle-shadow))}.default-handle path{stroke:var(--default-handle-color)}.vertical .first-overlay-container{clip-path:inset(0 0 var(--exposure) 0)}.vertical .handle-container{transform:translateY(50%);height:auto;top:unset;bottom:var(--exposure);width:100%;left:0;flex-direction:row}.vertical .divider:after{height:1px;width:100%;border-top-width:var(--divider-width);border-top-style:solid;border-top-color:var(--divider-color);border-left:0}.vertical .handle{top:auto;left:var(--handle-position-start);transform:translate(calc(-50% - 0.5px), -50%) rotate(90deg)}\", \"\"]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","\"use strict\";\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\n// eslint-disable-next-line func-names\nmodule.exports = function (cssWithMappingToString) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = cssWithMappingToString(item);\n\n if (item[2]) {\n return \"@media \".concat(item[2], \" {\").concat(content, \"}\");\n }\n\n return content;\n }).join(\"\");\n }; // import a list of modules into the list\n // eslint-disable-next-line func-names\n\n\n list.i = function (modules, mediaQuery, dedupe) {\n if (typeof modules === \"string\") {\n // eslint-disable-next-line no-param-reassign\n modules = [[null, modules, \"\"]];\n }\n\n var alreadyImportedModules = {};\n\n if (dedupe) {\n for (var i = 0; i < this.length; i++) {\n // eslint-disable-next-line prefer-destructuring\n var id = this[i][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n\n for (var _i = 0; _i < modules.length; _i++) {\n var item = [].concat(modules[_i]);\n\n if (dedupe && alreadyImportedModules[item[0]]) {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (mediaQuery) {\n if (!item[2]) {\n item[2] = mediaQuery;\n } else {\n item[2] = \"\".concat(mediaQuery, \" and \").concat(item[2]);\n }\n }\n\n list.push(item);\n }\n };\n\n return list;\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// Module\nvar code = \"
\";\n// Exports\nexport default code;","export const TABINDEX = 0;\nexport const RENDERED_CLASS = 'rendered';\n","export const isMouseEvent = (event) => {\n return event.type === 'mousedown';\n};\nexport const isElementAffected = (element, e) => {\n const rect = element.getBoundingClientRect();\n let eventX, eventY;\n if (isMouseEvent(e)) {\n eventX = e.clientX;\n eventY = e.clientY;\n }\n else {\n eventX = e.touches[0].clientX;\n eventY = e.touches[0].clientY;\n }\n return (eventX >= rect.x &&\n eventX <= rect.x + rect.width &&\n eventY >= rect.y &&\n eventY <= rect.y + rect.height);\n};\n","import styles from './styles.scss';\nimport { inBetween } from './inBetween';\nimport templateHtml from './template.html';\nimport { TABINDEX, RENDERED_CLASS } from './defaults';\nimport { isElementAffected } from './isElementAffected';\nlet templateElement;\nconst KeySlideOffset = {\n ArrowLeft: -1,\n ArrowRight: 1,\n};\nconst slideDirections = ['horizontal', 'vertical'];\nconst getTouchPagePoint = (e) => ({\n x: e.touches[0].pageX,\n y: e.touches[0].pageY,\n});\nconst getMousePagePoint = (e) => ({\n x: e.pageX,\n y: e.pageY,\n});\nconst slideAnimationPeriod = 1000 / 60;\nconst HTMLElement = typeof window !== 'undefined' && (window === null || window === void 0 ? void 0 : window.HTMLElement);\nexport class HTMLImgComparisonSliderElement extends HTMLElement {\n constructor() {\n super();\n this.exposure = this.hasAttribute('value')\n ? parseFloat(this.getAttribute('value'))\n : 50;\n this.slideOnHover = false;\n this.slideDirection = 'horizontal';\n this.keyboard = 'enabled';\n this.isMouseDown = false;\n this.animationDirection = 0;\n this.isFocused = false;\n this.dragByHandle = false;\n this.onMouseMove = (e) => {\n if (this.isMouseDown || this.slideOnHover) {\n const currentPoint = getMousePagePoint(e);\n this.slideToPage(currentPoint);\n }\n };\n this.bodyUserSelectStyle = '';\n this.bodyWebkitUserSelectStyle = '';\n this.onMouseDown = (e) => {\n if (this.slideOnHover) {\n return;\n }\n if (this.handle && !isElementAffected(this.handleElement, e)) {\n return;\n }\n e.preventDefault();\n window.addEventListener('mousemove', this.onMouseMove);\n window.addEventListener('mouseup', this.onWindowMouseUp);\n this.isMouseDown = true;\n this.enableTransition();\n const currentPoint = getMousePagePoint(e);\n this.slideToPage(currentPoint);\n this.focus();\n this.bodyUserSelectStyle = window.document.body.style.userSelect;\n this.bodyWebkitUserSelectStyle =\n window.document.body.style.webkitUserSelect;\n window.document.body.style.userSelect = 'none';\n window.document.body.style.webkitUserSelect = 'none';\n };\n this.onWindowMouseUp = () => {\n this.isMouseDown = false;\n window.document.body.style.userSelect = this.bodyUserSelectStyle;\n window.document.body.style.webkitUserSelect =\n this.bodyWebkitUserSelectStyle;\n window.removeEventListener('mousemove', this.onMouseMove);\n window.removeEventListener('mouseup', this.onWindowMouseUp);\n };\n this.touchStartPoint = null;\n this.isTouchComparing = false;\n this.hasTouchMoved = false;\n this.onTouchStart = (e) => {\n if (this.dragByHandle && !isElementAffected(this.handleElement, e)) {\n return;\n }\n this.touchStartPoint = getTouchPagePoint(e);\n if (this.isFocused) {\n this.enableTransition();\n this.slideToPage(this.touchStartPoint);\n }\n };\n this.onTouchMove = (e) => {\n if (this.touchStartPoint === null) {\n return;\n }\n const currentPoint = getTouchPagePoint(e);\n if (this.isTouchComparing) {\n this.slideToPage(currentPoint);\n e.preventDefault();\n return false;\n }\n if (!this.hasTouchMoved) {\n const offsetY = Math.abs(currentPoint.y - this.touchStartPoint.y);\n const offsetX = Math.abs(currentPoint.x - this.touchStartPoint.x);\n if ((this.slideDirection === 'horizontal' && offsetY < offsetX) ||\n (this.slideDirection === 'vertical' && offsetY > offsetX)) {\n this.isTouchComparing = true;\n this.focus();\n this.slideToPage(currentPoint);\n e.preventDefault();\n return false;\n }\n this.hasTouchMoved = true;\n }\n };\n this.onTouchEnd = () => {\n this.isTouchComparing = false;\n this.hasTouchMoved = false;\n this.touchStartPoint = null;\n };\n this.onBlur = () => {\n this.stopSlideAnimation();\n this.isFocused = false;\n this.firstElement.classList.remove('focused');\n };\n this.onFocus = () => {\n this.isFocused = true;\n this.firstElement.classList.add('focused');\n };\n this.onKeyDown = (e) => {\n if (this.keyboard === 'disabled') {\n return;\n }\n const direction = KeySlideOffset[e.key];\n if (this.animationDirection === direction) {\n return;\n }\n if (direction === undefined) {\n return;\n }\n this.animationDirection = direction;\n this.startSlideAnimation();\n };\n this.onKeyUp = (e) => {\n if (this.keyboard === 'disabled') {\n return;\n }\n const direction = KeySlideOffset[e.key];\n if (direction === undefined) {\n return;\n }\n if (this.animationDirection !== direction) {\n return;\n }\n this.stopSlideAnimation();\n };\n this.resetDimensions = () => {\n this.imageWidth = this.offsetWidth;\n this.imageHeight = this.offsetHeight;\n };\n const shadowRoot = this.attachShadow({ mode: 'open' });\n const styleEl = document.createElement('style');\n styleEl.innerHTML = styles;\n if (this.getAttribute('nonce')) {\n styleEl.setAttribute('nonce', this.getAttribute('nonce'));\n }\n shadowRoot.appendChild(styleEl);\n shadowRoot.appendChild(templateElement.content.cloneNode(true));\n this.firstElement = shadowRoot.getElementById('first');\n this.handleElement = shadowRoot.getElementById('handle');\n }\n set handle(newValue) {\n this.dragByHandle = newValue.toString().toLowerCase() !== 'false';\n }\n get handle() {\n return this.dragByHandle;\n }\n get value() {\n return this.exposure;\n }\n set value(newValue) {\n const newExposure = parseFloat(newValue);\n if (newExposure === this.exposure) {\n return;\n }\n this.exposure = newExposure;\n this.enableTransition();\n this.setExposure();\n }\n get hover() {\n return this.slideOnHover;\n }\n set hover(newValue) {\n this.slideOnHover = newValue.toString().toLowerCase() !== 'false';\n this.removeEventListener('mousemove', this.onMouseMove);\n if (this.slideOnHover) {\n this.addEventListener('mousemove', this.onMouseMove);\n }\n }\n get direction() {\n return this.slideDirection;\n }\n set direction(newValue) {\n this.slideDirection = newValue.toString().toLowerCase();\n this.slide(0);\n this.firstElement.classList.remove(...slideDirections);\n if (!slideDirections.includes(this.slideDirection)) {\n return;\n }\n this.firstElement.classList.add(this.slideDirection);\n }\n static get observedAttributes() {\n return ['hover', 'direction'];\n }\n connectedCallback() {\n if (!this.hasAttribute('tabindex')) {\n this.tabIndex = TABINDEX;\n }\n this.addEventListener('dragstart', (e) => {\n e.preventDefault();\n return false;\n });\n const resizeObserver = new ResizeObserver(this.resetDimensions);\n resizeObserver.observe(this);\n this.setExposure(0);\n this.keyboard =\n this.hasAttribute('keyboard') &&\n this.getAttribute('keyboard') === 'disabled'\n ? 'disabled'\n : 'enabled';\n this.addEventListener('keydown', this.onKeyDown);\n this.addEventListener('keyup', this.onKeyUp);\n this.addEventListener('focus', this.onFocus);\n this.addEventListener('blur', this.onBlur);\n this.addEventListener('touchstart', this.onTouchStart, {\n passive: true,\n });\n this.addEventListener('touchmove', this.onTouchMove, {\n passive: false,\n });\n this.addEventListener('touchend', this.onTouchEnd);\n this.addEventListener('mousedown', this.onMouseDown);\n this.handle = this.hasAttribute('handle')\n ? this.getAttribute('handle')\n : this.dragByHandle;\n this.hover = this.hasAttribute('hover')\n ? this.getAttribute('hover')\n : this.slideOnHover;\n this.direction = this.hasAttribute('direction')\n ? this.getAttribute('direction')\n : this.slideDirection;\n this.resetDimensions();\n if (!this.classList.contains(RENDERED_CLASS)) {\n this.classList.add(RENDERED_CLASS);\n }\n }\n disconnectedCallback() {\n if (this.transitionTimer) {\n window.clearTimeout(this.transitionTimer);\n }\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (name === 'hover') {\n this.hover = newValue;\n }\n if (name === 'direction') {\n this.direction = newValue;\n }\n if (name === 'keyboard') {\n this.keyboard = newValue === 'disabled' ? 'disabled' : 'enabled';\n }\n }\n setExposure(increment = 0) {\n this.exposure = inBetween(this.exposure + increment, 0, 100);\n this.firstElement.style.setProperty('--exposure', `${100 - this.exposure}%`);\n }\n slide(increment = 0) {\n this.setExposure(increment);\n const event = new Event('slide');\n this.dispatchEvent(event);\n }\n slideToPage(currentPoint) {\n if (this.slideDirection === 'horizontal') {\n this.slideToPageX(currentPoint.x);\n }\n if (this.slideDirection === 'vertical') {\n this.slideToPageY(currentPoint.y);\n }\n }\n slideToPageX(pageX) {\n const x = pageX - this.getBoundingClientRect().left - window.scrollX;\n this.exposure = (x / this.imageWidth) * 100;\n this.slide(0);\n }\n slideToPageY(pageY) {\n const y = pageY - this.getBoundingClientRect().top - window.scrollY;\n this.exposure = (y / this.imageHeight) * 100;\n this.slide(0);\n }\n enableTransition() {\n const transitionTime = 100;\n this.firstElement.style.setProperty('--transition-time', `${transitionTime}ms`);\n this.transitionTimer = window.setTimeout(() => {\n this.firstElement.style.setProperty('--transition-time', `var(--default-transition-time)`);\n this.transitionTimer = null;\n }, transitionTime);\n }\n startSlideAnimation() {\n let lastTimestamp = null;\n let initialDirection = this.animationDirection;\n this.firstElement.style.setProperty('--transition-time', `var(--keyboard-transition-time)`);\n const slide = (now) => {\n if (this.animationDirection === 0 ||\n initialDirection !== this.animationDirection) {\n return;\n }\n if (lastTimestamp === null) {\n lastTimestamp = now;\n }\n const interval = now - lastTimestamp, distance = (interval / slideAnimationPeriod) * this.animationDirection;\n this.slide(distance);\n // This is necessary to speed up the key up event in Desktop Safari\n setTimeout(() => window.requestAnimationFrame(slide), 0);\n lastTimestamp = now;\n };\n window.requestAnimationFrame(slide);\n }\n stopSlideAnimation() {\n this.animationDirection = 0;\n this.firstElement.style.setProperty('--transition-time', `var(--default-transition-time)`);\n }\n}\nif (typeof window !== 'undefined') {\n if (window.document) {\n templateElement = document.createElement('template');\n templateElement.innerHTML = templateHtml;\n }\n window.customElements.define('img-comparison-slider', HTMLImgComparisonSliderElement);\n}\n","export const inBetween = (actual, min, max) => {\n if (actual < min) {\n return min;\n }\n if (actual > max) {\n return max;\n }\n return actual;\n};\n"],"names":["___CSS_LOADER_EXPORT___","i","push","module","id","exports","cssWithMappingToString","list","toString","this","map","item","content","concat","join","modules","mediaQuery","dedupe","alreadyImportedModules","length","_i","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","n","getter","__esModule","d","a","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","RENDERED_CLASS","isElementAffected","element","e","rect","getBoundingClientRect","eventX","eventY","type","clientX","clientY","touches","x","width","y","height","templateElement","KeySlideOffset","ArrowLeft","ArrowRight","slideDirections","getTouchPagePoint","pageX","pageY","getMousePagePoint","HTMLElement","window","document","createElement","innerHTML","customElements","define","constructor","super","exposure","hasAttribute","parseFloat","getAttribute","slideOnHover","slideDirection","keyboard","isMouseDown","animationDirection","isFocused","dragByHandle","onMouseMove","currentPoint","slideToPage","bodyUserSelectStyle","bodyWebkitUserSelectStyle","onMouseDown","handle","handleElement","preventDefault","addEventListener","onWindowMouseUp","enableTransition","focus","body","style","userSelect","webkitUserSelect","removeEventListener","touchStartPoint","isTouchComparing","hasTouchMoved","onTouchStart","onTouchMove","offsetY","Math","abs","offsetX","onTouchEnd","onBlur","stopSlideAnimation","firstElement","classList","remove","onFocus","add","onKeyDown","direction","startSlideAnimation","onKeyUp","resetDimensions","imageWidth","offsetWidth","imageHeight","offsetHeight","shadowRoot","attachShadow","mode","styleEl","styles","setAttribute","appendChild","cloneNode","getElementById","newValue","toLowerCase","value","newExposure","setExposure","hover","slide","includes","observedAttributes","connectedCallback","tabIndex","ResizeObserver","observe","passive","contains","disconnectedCallback","transitionTimer","clearTimeout","attributeChangedCallback","name","oldValue","increment","actual","setProperty","event","Event","dispatchEvent","slideToPageX","slideToPageY","left","scrollX","top","scrollY","setTimeout","lastTimestamp","initialDirection","now","distance","requestAnimationFrame"],"sourceRoot":""} \ No newline at end of file diff --git a/master/search/search_index.json b/master/search/search_index.json index 268856f..19c4006 100644 --- a/master/search/search_index.json +++ b/master/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"documentation/overview/","title":"Overview","text":"

WireGuard Portal is a simple, web-based configuration portal for WireGuard server management. The portal uses the WireGuard wgctrl library to manage existing VPN interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN connections.

The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.

"},{"location":"documentation/overview/#features","title":"Features","text":"
  • Self-hosted - the whole application is a single binary
  • Responsive multi-language web UI written in Vue.js
  • Automatically selects IP from the network pool assigned to the client
  • QR-Code for convenient mobile client configuration
  • Sends email to the client with QR-code and client config
  • Enable / Disable clients seamlessly
  • Generation of wg-quick configuration file (wgX.conf) if required
  • User authentication (database, OAuth, or LDAP), Passkey support
  • IPv6 ready
  • Docker ready
  • Can be used with existing WireGuard setups
  • Support for multiple WireGuard interfaces
  • Supports multiple WireGuard backends (wgctrl or MikroTik [BETA])
  • Peer Expiry Feature
  • Handles route and DNS settings like wg-quick does
  • Exposes Prometheus metrics for monitoring and alerting
  • REST API for management and client deployment
  • Webhook for custom actions on peer, interface, or user updates
"},{"location":"documentation/configuration/examples/","title":"Examples","text":"

Below are some sample YAML configurations demonstrating how to override some default values.

"},{"location":"documentation/configuration/examples/#basic","title":"Basic","text":"
core:\n  admin_user: test@example.com\n  admin_password: password\n  admin_api_token: super-s3cr3t-api-token-or-a-UUID\n  import_existing: false\n  create_default_peer: true\n  self_provisioning_allowed: true\n\nbackend:\n  # default backend decides where new interfaces are created\n  default: mikrotik\n\n  mikrotik:\n    - id: mikrotik                   # unique id, not \"local\"\n      display_name: RouterOS RB5009  # optional nice name\n      api_url: https://10.10.10.10/rest\n      api_user: wgportal\n      api_password: a-super-secret-password\n      api_verify_tls: false        # set to false only if using self-signed during testing\n      api_timeout: 30s             # maximum request duration\n      concurrency: 5               # limit parallel REST calls to device\n      debug: false                 # verbose logging for this backend\n      ignored_interfaces:          # ignore these interfaces during import\n      - wgTest1\n      - wgTest2\n\nweb:\n  site_title: My WireGuard Server\n  site_company_name: My Company\n  listening_address: :8080\n  external_url: https://my.external-domain.com\n  csrf_secret: super-s3cr3t-csrf\n  session_secret: super-s3cr3t-session\n  request_logging: true\n\nadvanced:\n  log_level: trace\n  log_pretty: true\n  log_json: false\n  config_storage_path: /etc/wireguard\n  expiry_check_interval: 5m\n\ndatabase:\n  debug: true\n  type: sqlite\n  dsn: data/sqlite.db\n  encryption_passphrase: change-this-s3cr3t-encryption-passphrase\n\nauth:\n  webauthn:\n    enabled: true\n
"},{"location":"documentation/configuration/examples/#ldap-authentication-and-synchronization","title":"LDAP Authentication and Synchronization","text":"
# ... (basic configuration)\n\nauth:\n  ldap:\n    # a sample LDAP provider with user sync enabled\n    - id: ldap\n      provider_name: Active Directory\n      url: ldap://srv-ad1.company.local:389\n      bind_user: ldap_wireguard@company.local\n      bind_pass: super-s3cr3t-ldap\n      base_dn: DC=COMPANY,DC=LOCAL\n      login_filter: (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))\n      sync_interval: 15m\n      sync_filter: (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))\n      disable_missing: true\n      field_map:\n        user_identifier: sAMAccountName\n        email: mail\n        firstname: givenName\n        lastname: sn\n        phone: telephoneNumber\n        department: department\n        memberof: memberOf\n      admin_group: CN=WireGuardAdmins,OU=Some-OU,DC=COMPANY,DC=LOCAL\n      registration_enabled: true\n      log_user_info: true\n
"},{"location":"documentation/configuration/examples/#openid-connect-oidc-authentication","title":"OpenID Connect (OIDC) Authentication","text":"
# ... (basic configuration)\n\nauth:\n  oidc:\n    # A sample Entra ID provider with environment variable substitution.\n    # Only users with an @outlook.com email address are allowed to register or login.\n    - id: azure\n      provider_name: azure\n      display_name: Login with</br>Entra ID\n      registration_enabled: true\n      base_url: \"https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0\"\n      client_id: \"${AZURE_CLIENT_ID}\"\n      client_secret: \"${AZURE_CLIENT_SECRET}\"\n      allowed_domains:\n        - \"outlook.com\"\n      extra_scopes:\n        - profile\n        - email\n\n    # a sample provider where users with the attribute `wg_admin` set to `true` are considered as admins\n    - id: oidc-with-admin-attribute\n      provider_name: google\n      display_name: Login with</br>Google\n      base_url: https://accounts.google.com\n      client_id: the-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      extra_scopes:\n        - https://www.googleapis.com/auth/userinfo.email\n        - https://www.googleapis.com/auth/userinfo.profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: given_name\n        lastname: family_name\n        phone: phone_number\n        department: department\n        is_admin: wg_admin\n      admin_mapping:\n        admin_value_regex: ^true$\n      registration_enabled: true\n      log_user_info: true\n\n    # a sample provider where users in the group `the-admin-group` are considered as admins\n    - id: oidc-with-admin-group\n      provider_name: google2\n      display_name: Login with</br>Google2\n      base_url: https://accounts.google.com\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      extra_scopes:\n        - https://www.googleapis.com/auth/userinfo.email\n        - https://www.googleapis.com/auth/userinfo.profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: given_name\n        lastname: family_name\n        phone: phone_number\n        department: department\n        user_groups: groups\n      admin_mapping:\n        admin_group_regex: ^the-admin-group$\n      registration_enabled: true\n      log_user_info: true\n
"},{"location":"documentation/configuration/examples/#plain-oauth2-authentication","title":"Plain OAuth2 Authentication","text":"
# ... (basic configuration)\n\nauth:\n  oauth:\n    # a sample provider where users with the attribute `this-attribute-must-be-true` set to `true` or `True`\n    # are considered as admins\n    - id: google_plain_oauth-with-admin-attribute\n      provider_name: google3\n      display_name: Login with</br>Google3\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      auth_url: https://accounts.google.com/o/oauth2/v2/auth\n      token_url: https://oauth2.googleapis.com/token\n      user_info_url: https://openidconnect.googleapis.com/v1/userinfo\n      scopes:\n        - openid\n        - email\n        - profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: name\n        is_admin: this-attribute-must-be-true\n      admin_mapping:\n        admin_value_regex: ^(True|true)$\n      registration_enabled: true\n\n    # a sample provider where either users with the attribute `this-attribute-must-be-true` set to `true` or \n    # users in the group `admin-group-name` are considered as admins\n    - id: google_plain_oauth_with_groups\n      provider_name: google4\n      display_name: Login with</br>Google4\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      auth_url: https://accounts.google.com/o/oauth2/v2/auth\n      token_url: https://oauth2.googleapis.com/token\n      user_info_url: https://openidconnect.googleapis.com/v1/userinfo\n      scopes:\n        - openid\n        - email\n        - profile\n        - i-want-some-groups\n      field_map:\n        email: email\n        firstname: name\n        user_identifier: sub\n        is_admin: this-attribute-must-be-true\n        user_groups: groups\n      admin_mapping:\n        admin_value_regex: ^true$\n        admin_group_regex: ^admin-group-name$\n      registration_enabled: true\n      log_user_info: true\n

For more information, check out the usage documentation (e.g. General Configuration or Backends Configuration).

"},{"location":"documentation/configuration/overview/","title":"Overview","text":"

This page provides an overview of all available configuration options for WireGuard Portal.

You can supply these configurations in a YAML file when starting the Portal. The path of the configuration file defaults to config/config.yaml (or config/config.yml) in the working directory of the executable. It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG. For example: WG_PORTAL_CONFIG=/etc/wg-portal/config.yaml ./wg-portal. Also, environment variable substitution in the config file is supported. Refer to the syntax.

Configuration examples are available on the Examples page.

Default configuration
core:\n  admin_user: admin@wgportal.local\n  admin_password: wgportal-default\n  admin_api_token: \"\"\n  disable_admin_user: false\n  editable_keys: true\n  create_default_peer: false\n  create_default_peer_on_creation: false\n  re_enable_peer_after_user_enable: true\n  delete_peer_after_user_deleted: false\n  self_provisioning_allowed: false\n  import_existing: true\n  restore_state: true\n\nbackend:\n  default: local\n\nadvanced:\n  log_level: info\n  log_pretty: false\n  log_json: false\n  start_listen_port: 51820\n  start_cidr_v4: 10.11.12.0/24\n  start_cidr_v6: fdfd:d3ad:c0de:1234::0/64\n  use_ip_v6: true\n  config_storage_path: \"\"\n  expiry_check_interval: 15m\n  rule_prio_offset: 20000\n  route_table_offset: 20000\n  api_admin_only: true\n  limit_additional_user_peers: 0\n\ndatabase:\n  debug: false\n  slow_query_threshold: \"0\"\n  type: sqlite\n  dsn: data/sqlite.db\n  encryption_passphrase: \"\"\n\nstatistics:\n  use_ping_checks: true\n  ping_check_workers: 10\n  ping_unprivileged: false\n  ping_check_interval: 1m\n  data_collection_interval: 1m\n  collect_interface_data: true\n  collect_peer_data: true\n  collect_audit_data: true\n  listening_address: :8787\n\nmail:\n  host: 127.0.0.1\n  port: 25\n  encryption: none\n  cert_validation: true\n  username: \"\"\n  password: \"\"\n  auth_type: plain\n  from: Wireguard Portal <noreply@wireguard.local>\n  link_only: false\n\nauth:\n  oidc: []\n  oauth: []\n  ldap: []\n  webauthn:\n    enabled: true\n  min_password_length: 16\n  hide_login_form: false\n\nweb:\n  listening_address: :8888\n  external_url: http://localhost:8888\n  site_company_name: WireGuard Portal\n  site_title: WireGuard Portal\n  session_identifier: wgPortalSession\n  session_secret: very_secret\n  csrf_secret: extremely_secret\n  request_logging: false\n  expose_host_info: false\n  cert_file: \"\"\n  key_File: \"\"\n\nwebhook:\n  url: \"\"\n  authentication: \"\"\n  timeout: 10s\n

Below you will find sections like core, backend, advanced, database, statistics, mail, auth, web and webhook. Each section describes the individual configuration keys, their default values, and a brief explanation of their purpose.

"},{"location":"documentation/configuration/overview/#core","title":"Core","text":"

These are the primary configuration options that control fundamental WireGuard Portal behavior. More advanced options are found in the subsequent Advanced section.

"},{"location":"documentation/configuration/overview/#admin_user","title":"admin_user","text":"
  • Default: admin@wgportal.local
  • Description: The administrator user. This user will be created as a default admin if it does not yet exist.
"},{"location":"documentation/configuration/overview/#admin_password","title":"admin_password","text":"
  • Default: wgportal-default
  • Description: The administrator password. The default password should be changed immediately!
  • Important: The password should be strong and secure. The minimum password length is specified in auth.min_password_length. By default, it is 16 characters.
"},{"location":"documentation/configuration/overview/#disable_admin_user","title":"disable_admin_user","text":"
  • Default: false
  • Description: If true, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth.
"},{"location":"documentation/configuration/overview/#admin_api_token","title":"admin_api_token","text":"
  • Default: (empty)
  • Description: An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user.
"},{"location":"documentation/configuration/overview/#editable_keys","title":"editable_keys","text":"
  • Default: true
  • Description: Allow editing of WireGuard key-pairs directly in the UI.
"},{"location":"documentation/configuration/overview/#create_default_peer","title":"create_default_peer","text":"
  • Default: false
  • Description: If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces.
"},{"location":"documentation/configuration/overview/#create_default_peer_on_creation","title":"create_default_peer_on_creation","text":"
  • Default: false
  • Description: If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces.
"},{"location":"documentation/configuration/overview/#re_enable_peer_after_user_enable","title":"re_enable_peer_after_user_enable","text":"
  • Default: true
  • Description: Re-enable all peers that were previously disabled if the associated user is re-enabled.
"},{"location":"documentation/configuration/overview/#delete_peer_after_user_deleted","title":"delete_peer_after_user_deleted","text":"
  • Default: false
  • Description: If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled.
"},{"location":"documentation/configuration/overview/#self_provisioning_allowed","title":"self_provisioning_allowed","text":"
  • Default: false
  • Description: Allow registered (non-admin) users to self-provision peers from their profile page.
"},{"location":"documentation/configuration/overview/#import_existing","title":"import_existing","text":"
  • Default: true
  • Description: On startup, import existing WireGuard interfaces and peers into WireGuard Portal.
"},{"location":"documentation/configuration/overview/#restore_state","title":"restore_state","text":"
  • Default: true
  • Description: Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started.
"},{"location":"documentation/configuration/overview/#backend","title":"Backend","text":"

Configuration options for the WireGuard backend, which manages the WireGuard interfaces and peers. The current MikroTik backend is in BETA and may not support all features.

"},{"location":"documentation/configuration/overview/#default","title":"default","text":"
  • Default: local
  • Description: The default backend to use for managing WireGuard interfaces. Valid options are: local, or other backend id's configured in the mikrotik section.
"},{"location":"documentation/configuration/overview/#ignored_local_interfaces","title":"ignored_local_interfaces","text":"
  • Default: (empty)
  • Description: A list of interface names to exclude when enumerating local interfaces. This is useful if you want to prevent certain interfaces from being imported from the local system.
"},{"location":"documentation/configuration/overview/#mikrotik","title":"Mikrotik","text":"

The mikrotik array contains a list of MikroTik backend definitions. Each entry describes how to connect to a MikroTik RouterOS instance that hosts WireGuard interfaces.

Below are the properties for each entry inside backend.mikrotik:

"},{"location":"documentation/configuration/overview/#id","title":"id","text":"
  • Default: (empty)
  • Description: A unique identifier for this backend. This value can be referenced by backend.default to use this backend as default. The identifier must be unique across all backends and must not use the reserved keyword local.
"},{"location":"documentation/configuration/overview/#display_name","title":"display_name","text":"
  • Default: (empty)
  • Description: A human-friendly display name for this backend. If omitted, the id will be used as the display name.
"},{"location":"documentation/configuration/overview/#api_url","title":"api_url","text":"
  • Default: (empty)
  • Description: Base URL of the MikroTik REST API, including scheme and path, e.g., https://10.10.10.10:8729/rest.
"},{"location":"documentation/configuration/overview/#api_user","title":"api_user","text":"
  • Default: (empty)
  • Description: Username for authenticating against the MikroTik API. Ensure that the user has sufficient permissions to manage WireGuard interfaces and peers.
"},{"location":"documentation/configuration/overview/#api_password","title":"api_password","text":"
  • Default: (empty)
  • Description: Password for the specified API user.
"},{"location":"documentation/configuration/overview/#api_verify_tls","title":"api_verify_tls","text":"
  • Default: false
  • Description: Whether to verify the TLS certificate of the MikroTik API endpoint. Set to false to allow self-signed certificates (not recommended for production).
"},{"location":"documentation/configuration/overview/#api_timeout","title":"api_timeout","text":"
  • Default: 30s
  • Description: Timeout for API requests to the MikroTik device. Uses Go duration format (e.g., 10s, 1m). If omitted, a default of 30 seconds is used.
"},{"location":"documentation/configuration/overview/#concurrency","title":"concurrency","text":"
  • Default: 5
  • Description: Maximum number of concurrent API requests the backend will issue when enumerating interfaces and their details. If 0 or negative, a sane default of 5 is used.
"},{"location":"documentation/configuration/overview/#ignored_interfaces","title":"ignored_interfaces","text":"
  • Default: (empty)
  • Description: A list of interface names to exclude during interface enumeration. This is useful if you want to prevent specific interfaces from being imported from the MikroTik device.
"},{"location":"documentation/configuration/overview/#debug","title":"debug","text":"
  • Default: false
  • Description: Enable verbose debug logging for the MikroTik backend.

For more details on configuring the MikroTik backend, see the Backends documentation.

"},{"location":"documentation/configuration/overview/#advanced","title":"Advanced","text":"

Additional or more specialized configuration options for logging and interface creation details.

"},{"location":"documentation/configuration/overview/#log_level","title":"log_level","text":"
  • Default: info
  • Description: The log level used by the application. Valid options are: trace, debug, info, warn, error.
"},{"location":"documentation/configuration/overview/#log_pretty","title":"log_pretty","text":"
  • Default: false
  • Description: If true, log messages are colorized and formatted for readability (pretty-print).
"},{"location":"documentation/configuration/overview/#log_json","title":"log_json","text":"
  • Default: false
  • Description: If true, log messages are structured in JSON format.
"},{"location":"documentation/configuration/overview/#start_listen_port","title":"start_listen_port","text":"
  • Default: 51820
  • Description: The first port to use when automatically creating new WireGuard interfaces.
"},{"location":"documentation/configuration/overview/#start_cidr_v4","title":"start_cidr_v4","text":"
  • Default: 10.11.12.0/24
  • Description: The initial IPv4 subnet to use when automatically creating new WireGuard interfaces.
"},{"location":"documentation/configuration/overview/#start_cidr_v6","title":"start_cidr_v6","text":"
  • Default: fdfd:d3ad:c0de:1234::0/64
  • Description: The initial IPv6 subnet to use when automatically creating new WireGuard interfaces.
"},{"location":"documentation/configuration/overview/#use_ip_v6","title":"use_ip_v6","text":"
  • Default: true
  • Description: Enable or disable IPv6 support.
"},{"location":"documentation/configuration/overview/#config_storage_path","title":"config_storage_path","text":"
  • Default: (empty)
  • Description: Path to a directory where wg-quick style configuration files will be stored (if you need local filesystem configs).
"},{"location":"documentation/configuration/overview/#expiry_check_interval","title":"expiry_check_interval","text":"
  • Default: 15m
  • Description: Interval after which existing peers are checked if they are expired. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
"},{"location":"documentation/configuration/overview/#rule_prio_offset","title":"rule_prio_offset","text":"
  • Default: 20000
  • Description: Offset for IP route rule priorities when configuring routing.
"},{"location":"documentation/configuration/overview/#route_table_offset","title":"route_table_offset","text":"
  • Default: 20000
  • Description: Offset for IP route table IDs when configuring routing.
"},{"location":"documentation/configuration/overview/#api_admin_only","title":"api_admin_only","text":"
  • Default: true
  • Description: If true, the public REST API is accessible only to admin users. The API docs live at /api/v1/doc.html.
"},{"location":"documentation/configuration/overview/#limit_additional_user_peers","title":"limit_additional_user_peers","text":"
  • Default: 0
  • Description: Limit additional peers a normal user can create. 0 means unlimited.
"},{"location":"documentation/configuration/overview/#database","title":"Database","text":"

Configuration for the underlying database used by WireGuard Portal. Supported databases include SQLite, MySQL, Microsoft SQL Server, and Postgres.

If sensitive values (like private keys) should be stored in an encrypted format, set the encryption_passphrase option.

"},{"location":"documentation/configuration/overview/#debug_1","title":"debug","text":"
  • Default: false
  • Description: If true, logs all database statements (verbose).
"},{"location":"documentation/configuration/overview/#slow_query_threshold","title":"slow_query_threshold","text":"
  • Default: \"0\"
  • Description: A time threshold (e.g., 100ms) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses s, ms for seconds, milliseconds, see time.ParseDuration. The value must be a string.
"},{"location":"documentation/configuration/overview/#type","title":"type","text":"
  • Default: sqlite
  • Description: The database type. Valid options: sqlite, mssql, mysql, postgres.
"},{"location":"documentation/configuration/overview/#dsn","title":"dsn","text":"
  • Default: data/sqlite.db
  • Description: The Data Source Name (DSN) for connecting to the database. For example:
    user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local\n
"},{"location":"documentation/configuration/overview/#encryption_passphrase","title":"encryption_passphrase","text":"
  • Default: (empty)
  • Description: Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set. Important: Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward. New or updated records will be encrypted; existing data remains in plaintext until it\u2019s next modified.
"},{"location":"documentation/configuration/overview/#statistics","title":"Statistics","text":"

Controls how WireGuard Portal collects and reports usage statistics, including ping checks and Prometheus metrics.

"},{"location":"documentation/configuration/overview/#use_ping_checks","title":"use_ping_checks","text":"
  • Default: true
  • Description: Enable periodic ping checks to verify that peers remain responsive.
"},{"location":"documentation/configuration/overview/#ping_check_workers","title":"ping_check_workers","text":"
  • Default: 10
  • Description: Number of parallel worker processes for ping checks.
"},{"location":"documentation/configuration/overview/#ping_unprivileged","title":"ping_unprivileged","text":"
  • Default: false
  • Description: If false, ping checks run without root privileges. This is currently considered BETA.
"},{"location":"documentation/configuration/overview/#ping_check_interval","title":"ping_check_interval","text":"
  • Default: 1m
  • Description: Interval between consecutive ping checks for all peers. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
"},{"location":"documentation/configuration/overview/#data_collection_interval","title":"data_collection_interval","text":"
  • Default: 1m
  • Description: Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
"},{"location":"documentation/configuration/overview/#collect_interface_data","title":"collect_interface_data","text":"
  • Default: true
  • Description: If true, collects interface-level data (bytes in/out) for monitoring and statistics.
"},{"location":"documentation/configuration/overview/#collect_peer_data","title":"collect_peer_data","text":"
  • Default: true
  • Description: If true, collects peer-level data (bytes, last handshake, endpoint, etc.).
"},{"location":"documentation/configuration/overview/#collect_audit_data","title":"collect_audit_data","text":"
  • Default: true
  • Description: If true, logs certain portal events (such as user logins) to the database.
"},{"location":"documentation/configuration/overview/#listening_address","title":"listening_address","text":"
  • Default: :8787
  • Description: Address and port for the integrated Prometheus metric server (e.g., :8787 or 127.0.0.1:8787).
"},{"location":"documentation/configuration/overview/#mail","title":"Mail","text":"

Options for configuring email notifications or sending peer configurations via email.

"},{"location":"documentation/configuration/overview/#host","title":"host","text":"
  • Default: 127.0.0.1
  • Description: Hostname or IP of the SMTP server.
"},{"location":"documentation/configuration/overview/#port","title":"port","text":"
  • Default: 25
  • Description: Port number for the SMTP server.
"},{"location":"documentation/configuration/overview/#encryption","title":"encryption","text":"
  • Default: none
  • Description: SMTP encryption type. Valid values: none, tls, starttls.
"},{"location":"documentation/configuration/overview/#cert_validation","title":"cert_validation","text":"
  • Default: true
  • Description: If true, validate the SMTP server certificate (relevant if encryption = tls).
"},{"location":"documentation/configuration/overview/#username","title":"username","text":"
  • Default: (empty)
  • Description: Optional SMTP username for authentication.
"},{"location":"documentation/configuration/overview/#password","title":"password","text":"
  • Default: (empty)
  • Description: Optional SMTP password for authentication.
"},{"location":"documentation/configuration/overview/#auth_type","title":"auth_type","text":"
  • Default: plain
  • Description: SMTP authentication type. Valid values: plain, login, crammd5.
"},{"location":"documentation/configuration/overview/#from","title":"from","text":"
  • Default: Wireguard Portal <noreply@wireguard.local>
  • Description: The default \"From\" address when sending emails.
"},{"location":"documentation/configuration/overview/#link_only","title":"link_only","text":"
  • Default: false
  • Description: If true, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
"},{"location":"documentation/configuration/overview/#auth","title":"Auth","text":"

WireGuard Portal supports multiple authentication strategies, including OpenID Connect (oidc), OAuth (oauth), Passkeys (webauthn) and LDAP (ldap). Each can have multiple providers configured. Below are the relevant keys.

Some core authentication options are shared across all providers, while others are specific to each provider type.

"},{"location":"documentation/configuration/overview/#min_password_length","title":"min_password_length","text":"
  • Default: 16
  • Description: Minimum password length for local authentication. This is not enforced for LDAP authentication. The default admin password strength is also enforced by this setting.
  • Important: The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.
"},{"location":"documentation/configuration/overview/#hide_login_form","title":"hide_login_form","text":"
  • Default: false
  • Description: If true, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method. If no social login providers are configured, the login form is always shown, regardless of this setting.
  • Important: You can still access the login form by adding the ?all query parameter to the login URL (e.g. https://wg.portal/#/login?all).
"},{"location":"documentation/configuration/overview/#oidc","title":"OIDC","text":"

The oidc array contains a list of OpenID Connect providers. Below are the properties for each OIDC provider entry inside auth.oidc:

"},{"location":"documentation/configuration/overview/#provider_name","title":"provider_name","text":"
  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.
"},{"location":"documentation/configuration/overview/#display_name_1","title":"display_name","text":"
  • Default: (empty)
  • Description: A user-friendly name shown on the login page (e.g., \"Login with Google\").
"},{"location":"documentation/configuration/overview/#base_url","title":"base_url","text":"
  • Default: (empty)
  • Description: The OIDC provider\u2019s base URL (e.g., https://accounts.google.com).
"},{"location":"documentation/configuration/overview/#client_id","title":"client_id","text":"
  • Default: (empty)
  • Description: The OAuth client ID from the OIDC provider.
"},{"location":"documentation/configuration/overview/#client_secret","title":"client_secret","text":"
  • Default: (empty)
  • Description: The OAuth client secret from the OIDC provider.
"},{"location":"documentation/configuration/overview/#extra_scopes","title":"extra_scopes","text":"
  • Default: (empty)
  • Description: A list of additional OIDC scopes (e.g., profile, email).
"},{"location":"documentation/configuration/overview/#allowed_domains","title":"allowed_domains","text":"
  • Default: (empty)
  • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
"},{"location":"documentation/configuration/overview/#field_map","title":"field_map","text":"
  • Default: (empty)
  • Description: Maps OIDC claims to WireGuard Portal user fields.
  • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

    Field Typical OIDC Claim Explanation user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it\u2019s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it\u2019s unique. email email The user\u2019s email address as provided by the IdP. Not always verified, depending on IdP settings. firstname given_name The user\u2019s first name, typically provided by the IdP in the given_name claim. lastname family_name The user\u2019s last (family) name, typically provided by the IdP in the family_name claim. phone phone_number The user\u2019s phone number. This may require additional scopes/permissions from the IdP to access. department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute). is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership. user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.
"},{"location":"documentation/configuration/overview/#admin_mapping","title":"admin_mapping","text":"
  • Default: (empty)
  • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
    • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string \"true\" (^true$).
    • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.
"},{"location":"documentation/configuration/overview/#registration_enabled","title":"registration_enabled","text":"
  • Default: (empty)
  • Description: If true, a new user will be created in WireGuard Portal if not already present.
"},{"location":"documentation/configuration/overview/#log_user_info","title":"log_user_info","text":"
  • Default: (empty)
  • Description: If true, OIDC user data is logged at the trace level upon login (for debugging).
"},{"location":"documentation/configuration/overview/#oauth","title":"OAuth","text":"

The oauth array contains a list of plain OAuth2 providers. Below are the properties for each OAuth provider entry inside auth.oauth:

"},{"location":"documentation/configuration/overview/#provider_name_1","title":"provider_name","text":"
  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.
"},{"location":"documentation/configuration/overview/#display_name_2","title":"display_name","text":"
  • Default: (empty)
  • Description: A user-friendly name shown on the login page.
"},{"location":"documentation/configuration/overview/#client_id_1","title":"client_id","text":"
  • Default: (empty)
  • Description: The OAuth client ID for the provider.
"},{"location":"documentation/configuration/overview/#client_secret_1","title":"client_secret","text":"
  • Default: (empty)
  • Description: The OAuth client secret for the provider.
"},{"location":"documentation/configuration/overview/#auth_url","title":"auth_url","text":"
  • Default: (empty)
  • Description: URL of the authentication endpoint.
"},{"location":"documentation/configuration/overview/#token_url","title":"token_url","text":"
  • Default: (empty)
  • Description: URL of the token endpoint.
"},{"location":"documentation/configuration/overview/#user_info_url","title":"user_info_url","text":"
  • Default: (empty)
  • Description: URL of the user information endpoint.
"},{"location":"documentation/configuration/overview/#scopes","title":"scopes","text":"
  • Default: (empty)
  • Description: A list of OAuth scopes.
"},{"location":"documentation/configuration/overview/#allowed_domains_1","title":"allowed_domains","text":"
  • Default: (empty)
  • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
"},{"location":"documentation/configuration/overview/#field_map_1","title":"field_map","text":"
  • Default: (empty)
  • Description: Maps OAuth attributes to WireGuard Portal fields.
  • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

    Field Typical Claim Explanation user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it\u2019s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it\u2019s unique. email email The user\u2019s email address as provided by the IdP. Not always verified, depending on IdP settings. firstname given_name The user\u2019s first name, typically provided by the IdP in the given_name claim. lastname family_name The user\u2019s last (family) name, typically provided by the IdP in the family_name claim. phone phone_number The user\u2019s phone number. This may require additional scopes/permissions from the IdP to access. department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute). is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership. user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.
"},{"location":"documentation/configuration/overview/#admin_mapping_1","title":"admin_mapping","text":"
  • Default: (empty)
  • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
  • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string \"true\" (^true$).
  • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.
"},{"location":"documentation/configuration/overview/#registration_enabled_1","title":"registration_enabled","text":"
  • Default: (empty)
  • Description: If true, new users are created automatically on successful login.
"},{"location":"documentation/configuration/overview/#log_user_info_1","title":"log_user_info","text":"
  • Default: (empty)
  • Description: If true, logs user info at the trace level upon login.
"},{"location":"documentation/configuration/overview/#ldap","title":"LDAP","text":"

The ldap array contains a list of LDAP authentication providers. Below are the properties for each LDAP provider entry inside auth.ldap:

"},{"location":"documentation/configuration/overview/#provider_name_2","title":"provider_name","text":"
  • Default: (empty)
  • Description: A unique name for this provider. Must not conflict with other providers.
"},{"location":"documentation/configuration/overview/#url","title":"url","text":"
  • Default: (empty)
  • Description: The LDAP server URL (e.g., ldap://srv-ad01.company.local:389).
"},{"location":"documentation/configuration/overview/#start_tls","title":"start_tls","text":"
  • Default: (empty)
  • Description: If true, use STARTTLS to secure the LDAP connection.
"},{"location":"documentation/configuration/overview/#cert_validation_1","title":"cert_validation","text":"
  • Default: (empty)
  • Description: If true, validate the LDAP server\u2019s TLS certificate.
"},{"location":"documentation/configuration/overview/#tls_certificate_path","title":"tls_certificate_path","text":"
  • Default: (empty)
  • Description: Path to a TLS certificate if needed for LDAP connections.
"},{"location":"documentation/configuration/overview/#tls_key_path","title":"tls_key_path","text":"
  • Default: (empty)
  • Description: Path to the corresponding TLS certificate key.
"},{"location":"documentation/configuration/overview/#base_dn","title":"base_dn","text":"
  • Default: (empty)
  • Description: The base DN for user searches (e.g., DC=COMPANY,DC=LOCAL).
"},{"location":"documentation/configuration/overview/#bind_user","title":"bind_user","text":"
  • Default: (empty)
  • Description: The bind user for LDAP (e.g., company\\\\ldap_wireguard or ldap_wireguard@company.local).
"},{"location":"documentation/configuration/overview/#bind_pass","title":"bind_pass","text":"
  • Default: (empty)
  • Description: The bind password for LDAP authentication.
"},{"location":"documentation/configuration/overview/#field_map_2","title":"field_map","text":"
  • Default: (empty)
  • Description: Maps LDAP attributes to WireGuard Portal fields.

    • Available fields: user_identifier, email, firstname, lastname, phone, department, memberof.
    WireGuard Portal Field Typical LDAP Attribute Short Description user_identifier sAMAccountName / uid Uniquely identifies the user within the LDAP directory. email mail / userPrincipalName Stores the user's primary email address. firstname givenName Contains the user's first (given) name. lastname sn Contains the user's last (surname) name. phone telephoneNumber / mobile Holds the user's phone or mobile number. department departmentNumber / ou Specifies the department or organizational unit of the user. memberof memberOf Lists the groups and roles to which the user belongs.
"},{"location":"documentation/configuration/overview/#login_filter","title":"login_filter","text":"
  • Default: (empty)
  • Description: An LDAP filter to restrict which users can log in. Use {{login_identifier}} to insert the username. For example:
    (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))\n
  • Important: The login_filter must always be a valid LDAP filter. It should at most return one user. If the filter returns multiple or no users, the login will fail.
"},{"location":"documentation/configuration/overview/#admin_group","title":"admin_group","text":"
  • Default: (empty)
  • Description: A specific LDAP group whose members are considered administrators in WireGuard Portal. For example:
    CN=WireGuardAdmins,OU=Some-OU,DC=YOURDOMAIN,DC=LOCAL\n
"},{"location":"documentation/configuration/overview/#sync_interval","title":"sync_interval","text":"
  • Default: (empty)
  • Description: How frequently (in duration, e.g. 30m) to synchronize users from LDAP. Empty or 0 disables sync. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration. Only users that match the sync_filter are synchronized, if disable_missing is true, users not found in LDAP are disabled.
"},{"location":"documentation/configuration/overview/#sync_filter","title":"sync_filter","text":"
  • Default: (empty)
  • Description: An LDAP filter to select which users get synchronized into WireGuard Portal. For example:
    (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))\n
"},{"location":"documentation/configuration/overview/#disable_missing","title":"disable_missing","text":"
  • Default: (empty)
  • Description: If true, any user not found in LDAP (during sync) is disabled in WireGuard Portal.
"},{"location":"documentation/configuration/overview/#auto_re_enable","title":"auto_re_enable","text":"
  • Default: (empty)
  • Description: If true, users that where disabled because they were missing (see disable_missing) will be re-enabled once they are found again.
"},{"location":"documentation/configuration/overview/#registration_enabled_2","title":"registration_enabled","text":"
  • Default: (empty)
  • Description: If true, new user accounts are created in WireGuard Portal upon first login.
"},{"location":"documentation/configuration/overview/#log_user_info_2","title":"log_user_info","text":"
  • Default: (empty)
  • Description: If true, logs LDAP user data at the trace level upon login.
"},{"location":"documentation/configuration/overview/#webauthn-passkeys","title":"WebAuthn (Passkeys)","text":"

The webauthn section contains configuration options for WebAuthn authentication (passkeys).

"},{"location":"documentation/configuration/overview/#enabled","title":"enabled","text":"
  • Default: true
  • Description: If true, Passkey authentication is enabled. If false, WebAuthn is disabled. Users are encouraged to use Passkeys for secure authentication instead of passwords. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.
"},{"location":"documentation/configuration/overview/#web","title":"Web","text":"

The web section contains configuration options for the web server, including the listening address, session management, and CSRF protection. It is important to specify a valid external_url for the web server, especially if you are using a reverse proxy. Without a valid external_url, the login process may fail due to CSRF protection.

"},{"location":"documentation/configuration/overview/#listening_address_1","title":"listening_address","text":"
  • Default: :8888
  • Description: The listening address and port for the web server (e.g., :8888 to bind on all interfaces or 127.0.0.1:8888 to bind only on the loopback interface). Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.
"},{"location":"documentation/configuration/overview/#external_url","title":"external_url","text":"
  • Default: http://localhost:8888
  • Description: The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. Important: If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.
"},{"location":"documentation/configuration/overview/#site_company_name","title":"site_company_name","text":"
  • Default: WireGuard Portal
  • Description: The company name that is shown at the bottom of the web frontend.
"},{"location":"documentation/configuration/overview/#site_title","title":"site_title","text":"
  • Default: WireGuard Portal
  • Description: The title that is shown in the web frontend.
"},{"location":"documentation/configuration/overview/#session_identifier","title":"session_identifier","text":"
  • Default: wgPortalSession
  • Description: The session identifier for the web frontend.
"},{"location":"documentation/configuration/overview/#session_secret","title":"session_secret","text":"
  • Default: very_secret
  • Description: The session secret for the web frontend.
"},{"location":"documentation/configuration/overview/#csrf_secret","title":"csrf_secret","text":"
  • Default: extremely_secret
  • Description: The CSRF secret.
"},{"location":"documentation/configuration/overview/#request_logging","title":"request_logging","text":"
  • Default: false
  • Description: Log all HTTP requests.
"},{"location":"documentation/configuration/overview/#expose_host_info","title":"expose_host_info","text":"
  • Default: false
  • Description: Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.
"},{"location":"documentation/configuration/overview/#cert_file","title":"cert_file","text":"
  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate file.
"},{"location":"documentation/configuration/overview/#key_file","title":"key_file","text":"
  • Default: (empty)
  • Description: (Optional) Path to the TLS certificate key file.
"},{"location":"documentation/configuration/overview/#webhook","title":"Webhook","text":"

The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. Further details can be found in the usage documentation.

"},{"location":"documentation/configuration/overview/#url_1","title":"url","text":"
  • Default: (empty)
  • Description: The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.
"},{"location":"documentation/configuration/overview/#authentication","title":"authentication","text":"
  • Default: (empty)
  • Description: The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: Bearer <token>.
"},{"location":"documentation/configuration/overview/#timeout","title":"timeout","text":"
  • Default: 10s
  • Description: The timeout for the webhook request. If the request takes longer than this, it is aborted.
"},{"location":"documentation/getting-started/binaries/","title":"Binaries","text":"

Starting from v2, each release includes compiled binaries for supported platforms. These binary versions can be manually downloaded and installed.

"},{"location":"documentation/getting-started/binaries/#download","title":"Download","text":"

Make sure that you download the correct binary for your architecture. The available binaries are:

  • wg-portal_linux_amd64 - Linux x86_64
  • wg-portal_linux_arm64 - Linux ARM 64-bit
  • wg-portal_linux_arm_v7 - Linux ARM 32-bit

With curl:

curl -L -o wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64 \n

With wget:

wget -O wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64\n

with gh cli:

gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'\n
"},{"location":"documentation/getting-started/binaries/#install","title":"Install","text":"
sudo mkdir -p /opt/wg-portal\nsudo install wg-portal /opt/wg-portal/\n
"},{"location":"documentation/getting-started/binaries/#unreleased-versions-master-branch-builds","title":"Unreleased versions (master branch builds)","text":"

Unreleased versions can be fetched directly from the artifacts section of the GitHub Workflow.

"},{"location":"documentation/getting-started/docker/","title":"Docker","text":""},{"location":"documentation/getting-started/docker/#image-usage","title":"Image Usage","text":"

The WireGuard Portal Docker image is available on both Docker Hub and GitHub Container Registry. It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.

This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the linuxserver/wireguard Docker image.

The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.

A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:

---\nservices:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    restart: unless-stopped\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    cap_add:\n      - NET_ADMIN\n    # Use host network mode for WireGuard and the UI. Ensure that access to the UI is properly secured.\n    network_mode: \"host\"\n    volumes:\n      # left side is the host path, right side is the container path\n      - /etc/wireguard:/etc/wireguard\n      - ./data:/app/data\n      - ./config:/app/config\n

By default, the webserver for the UI is listening on port 8888 on all available interfaces.

Volumes for /app/data and /app/config should be used ensure data persistence across container restarts.

"},{"location":"documentation/getting-started/docker/#wireguard-interface-handling","title":"WireGuard Interface Handling","text":"

WireGuard Portal supports managing WireGuard interfaces through three distinct deployment methods, providing flexibility based on your system architecture and operational preferences:

  • Directly on the host system: WireGuard Portal can control WireGuard interfaces natively on the host, without using containers. This setup is ideal for environments where direct access to system networking is preferred. To use this method, you need to set the network mode to host in your docker-compose.yml file.

    services:\n  wg-portal:\n    ...\n    network_mode: \"host\"\n    ...\n

    If host networking is used, the WireGuard Portal UI will be accessible on all the host's IP addresses if the listening address is set to :8888 in the configuration file. To avoid this, you can bind the listening address to a specific IP address, for example, the loopback address (127.0.0.1:8888). It is also possible to deploy firewall rules to restrict access to the WireGuard Portal UI.

  • Within the WireGuard Portal Docker container: WireGuard interfaces can be managed directly from within the WireGuard Portal container itself. This is the recommended approach when running WireGuard Portal via Docker, as it encapsulates all functionality in a single, portable container without requiring a separate WireGuard host or image.

    services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    ...\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      # WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)\n      - \"51820:51820/udp\" \n      # Web UI port\n      - \"8888:8888/tcp\"\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n    volumes:\n      # host path : container path\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n

  • Via a separate Docker container: WireGuard Portal can interface with and control WireGuard running in another Docker container, such as the linuxserver/wireguard image. This method is useful in setups that already use linuxserver/wireguard or where you want to isolate the VPN backend from the portal frontend. For this, you need to set the network mode to service:wireguard in your docker-compose.yml file, wireguard is the service name of your WireGuard container.

    services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    ...\n    cap_add:\n      - NET_ADMIN\n    network_mode: \"service:wireguard\" # So we ensure to stay on the same network as the wireguard container.\n    volumes:\n      # host path : container path\n      - ./wg/etc:/etc/wireguard\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n\n  wireguard:\n    image: lscr.io/linuxserver/wireguard:latest\n    container_name: wireguard\n    restart: unless-stopped\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      - \"51820:51820/udp\" # WireGuard port, needs to match the port in wg-portal interface config\n      - \"8888:8888/tcp\" # Noticed that the port of the web UI is exposed in the wireguard container.\n    volumes:\n      - ./wg/etc:/config/wg_confs # We share the configuration (wgx.conf) between wg-portal and wireguard\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n
    As the linuxserver/wireguard image uses wg-quick to manage the interfaces, you need to have at least the following configuration set for WireGuard Portal:
    core:\n  # The WireGuard container uses wg-quick to manage the WireGuard interfaces - this conflicts with WireGuard Portal during startup.\n  # To avoid this, we need to set the restore_state option to false so that wg-quick can create the interfaces.\n  restore_state: false\n  # Usually, there are no existing interfaces in the WireGuard container, so we can set this to false.\n  import_existing: false\nadvanced:\n  # WireGuard Portal needs to export the WireGuard configuration as wg-quick config files so that the WireGuard container can use them.\n  config_storage_path: /etc/wireguard/\n

"},{"location":"documentation/getting-started/docker/#image-versioning","title":"Image Versioning","text":"

All images are hosted on Docker Hub at https://hub.docker.com/r/wgportal/wg-portal or in the GitHub Container Registry.

Version 2 is the current stable release. Version 1 has moved to legacy status and is no longer recommended.

There are three types of tags in the repository:

"},{"location":"documentation/getting-started/docker/#semantic-versioned-tags","title":"Semantic versioned tags","text":"

For example, 2.0.0-rc.1 or v2.0.0-rc.1.

These are official releases of WireGuard Portal. For production deployments of WireGuard Portal, we strongly recommend using one of these versioned tags instead of the latest or canary tags.

There are different types of these tags:

  • Major version tags: v2 or 2. These tags always refer to the latest image for WireGuard Portal version 2.
  • Minor version tags: v2.x or 2.0. These tags always refer to the latest image for WireGuard Portal version 2.x.
  • Specific version tags (patch version): v2.0.0 or 2.0.0. These tags denote a very specific release. They correspond to the GitHub tags that we make, and you can see the release notes for them here: https://github.com/h44z/wg-portal/releases. Once these tags for a specific version show up in the Docker repository, they will never change.
"},{"location":"documentation/getting-started/docker/#the-latest-tag","title":"The latest tag","text":"

The lastest tag is the latest stable release of WireGuard Portal. For version 2, this is the same as the v2 tag.

"},{"location":"documentation/getting-started/docker/#the-master-tag","title":"The master tag","text":"

This is the most recent build to the main branch! It changes a lot and is very unstable.

We recommend that you don't use it except for development purposes or to test the latest features.

"},{"location":"documentation/getting-started/docker/#configuration","title":"Configuration","text":"

You can configure WireGuard Portal using a YAML configuration file. The filepath of the YAML configuration file defaults to /app/config/config.yaml. It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG.

By default, WireGuard Portal uses an SQLite database. The database is stored in /app/data/sqlite.db.

You should mount those directories as a volume:

  • /app/data
  • /app/config

A detailed description of the configuration options can be found here.

If you want to access configuration files in wg-quick format, you can mount the /etc/wireguard directory inside the container to a location of your choice. Also enable the config_storage_path option in the configuration file:

advanced:\n  config_storage_path: /etc/wireguard\n

"},{"location":"documentation/getting-started/helm/","title":"Helm","text":""},{"location":"documentation/getting-started/helm/#installing-the-chart","title":"Installing the Chart","text":"

To install the chart with the release name wg-portal:

helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal\n

This command deploy wg-portal on the Kubernetes cluster in the default configuration. The Values section lists the parameters that can be configured during installation.

"},{"location":"documentation/getting-started/helm/#values","title":"Values","text":"Key Type Default Description nameOverride string \"\" Partially override resource names (adds suffix) fullnameOverride string \"\" Fully override resource names extraDeploy list [] Array of extra objects to deploy with the release config.advanced tpl/object {} Advanced configuration options. config.auth tpl/object {} Auth configuration options. config.core tpl/object {} Core configuration options. If external admins in auth are defined and there are no admin_user and admin_password defined here, the default admin account will be disabled. config.database tpl/object {} Database configuration options config.mail tpl/object {} Mail configuration options config.statistics tpl/object {} Statistics configuration options config.web tpl/object {} Web configuration options. listening_address will be set automatically from service.web.port. external_url is required to enable ingress and certificate resources. revisionHistoryLimit string 10 The number of old ReplicaSets to retain to allow rollback. workloadType string \"Deployment\" Workload type - Deployment or StatefulSet strategy object {\"type\":\"RollingUpdate\"} Update strategy for the workload Valid values are: RollingUpdate or Recreate for Deployment, RollingUpdate or OnDelete for StatefulSet image.repository string \"ghcr.io/h44z/wg-portal\" Image repository image.pullPolicy string \"IfNotPresent\" Image pull policy image.tag string \"\" Overrides the image tag whose default is the chart appVersion imagePullSecrets list [] Image pull secrets podAnnotations tpl/object {} Extra annotations to add to the pod podLabels object {} Extra labels to add to the pod podSecurityContext object {} Pod Security Context securityContext.capabilities.add list [\"NET_ADMIN\"] Add capabilities to the container initContainers tpl/list [] Pod init containers sidecarContainers tpl/list [] Pod sidecar containers dnsPolicy string \"ClusterFirst\" Set DNS policy for the pod. Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. restartPolicy string \"Always\" Restart policy for all containers within the pod. Valid values are Always, OnFailure or Never. hostNetwork string false. Use the host's network namespace. resources object {} Resources requests and limits command list [] Overwrite pod command args list [] Additional pod arguments env tpl/list [] Additional environment variables envFrom tpl/list [] Additional environment variables from a secret or configMap livenessProbe object {} Liveness probe configuration readinessProbe object {} Readiness probe configuration startupProbe object {} Startup probe configuration volumes tpl/list [] Additional volumes volumeMounts tpl/list [] Additional volumeMounts nodeSelector object {\"kubernetes.io/os\":\"linux\"} Node Selector configuration tolerations list [] Tolerations configuration affinity object {} Affinity configuration service.mixed.enabled bool false Whether to create a single service for the web and wireguard interfaces service.mixed.type string \"LoadBalancer\" Service type service.web.annotations object {} Annotations for the web service service.web.type string \"ClusterIP\" Web service type service.web.port int 8888 Web service port Used for the web interface listener service.web.appProtocol string \"http\" Web service appProtocol. Will be auto set to https if certificate is enabled. service.wireguard.annotations object {} Annotations for the WireGuard service service.wireguard.type string \"LoadBalancer\" Wireguard service type service.wireguard.ports list [51820] Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. service.metrics.port int 8787 ingress.enabled bool false Specifies whether an ingress resource should be created ingress.className string \"\" Ingress class name ingress.annotations object {} Ingress annotations ingress.tls bool false Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret certificate.enabled bool false Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web. certificate.issuer.name string \"\" Certificate issuer name certificate.issuer.kind string \"\" Certificate issuer kind (ClusterIssuer or Issuer) certificate.issuer.group string \"cert-manager.io\" Certificate issuer group certificate.duration string \"\" Optional. Documentation certificate.renewBefore string \"\" Optional. Documentation certificate.commonName string \"\" Optional. Documentation certificate.emailAddresses list [] Optional. Documentation certificate.ipAddresses list [] Optional. Documentation certificate.keystores object {} Optional. Documentation certificate.privateKey object {} Optional. Documentation certificate.secretTemplate object {} Optional. Documentation certificate.subject object {} Optional. Documentation certificate.uris list [] Optional. Documentation certificate.usages list [] Optional. Documentation persistence.enabled bool false Specifies whether an persistent volume should be created persistence.annotations object {} Persistent Volume Claim annotations persistence.storageClass string \"\" Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used. persistence.accessMode string \"ReadWriteOnce\" Persistent Volume Access Mode persistence.size string \"1Gi\" Persistent Volume size persistence.volumeName string \"\" Persistent Volume Name (optional) serviceAccount.create bool true Specifies whether a service account should be created serviceAccount.annotations object {} Service account annotations serviceAccount.automount bool false Automatically mount a ServiceAccount's API credentials serviceAccount.name string \"\" The name of the service account to use. If not set and create is true, a name is generated using the fullname template monitoring.enabled bool false Enable Prometheus monitoring. monitoring.apiVersion string \"monitoring.coreos.com/v1\" API version of the Prometheus resource. Use azmonitoring.coreos.com/v1 for Azure Managed Prometheus. monitoring.kind string \"PodMonitor\" Kind of the Prometheus resource. Could be PodMonitor or ServiceMonitor. monitoring.labels object {} Resource labels. monitoring.annotations object {} Resource annotations. monitoring.interval string 1m Interval at which metrics should be scraped. If not specified config.statistics.data_collection_interval interval is used. monitoring.metricRelabelings list [] Relabelings to samples before ingestion. monitoring.relabelings list [] Relabelings to samples before scraping. monitoring.scrapeTimeout string \"\" Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used. monitoring.jobLabel string \"\" The label to use to retrieve the job name from. monitoring.podTargetLabels object {} Transfers labels on the Kubernetes Pod onto the target. monitoring.dashboard.enabled bool false Enable Grafana dashboard. monitoring.dashboard.annotations object {} Annotations for the dashboard ConfigMap. monitoring.dashboard.labels object {} Additional labels for the dashboard ConfigMap. monitoring.dashboard.namespace string \"\" Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap."},{"location":"documentation/getting-started/reverse-proxy/","title":"Reverse Proxy (HTTPS)","text":""},{"location":"documentation/getting-started/reverse-proxy/#reverse-proxy-for-https","title":"Reverse Proxy for HTTPS","text":"

For production deployments, always serve the WireGuard Portal over HTTPS. You have two options to secure your connection:

"},{"location":"documentation/getting-started/reverse-proxy/#reverse-proxy","title":"Reverse Proxy","text":"

Let a front\u2010end proxy handle HTTPS for you. This also frees you from managing certificates manually and is therefore the preferred option. You can use Nginx, Traefik, Caddy or any other proxy.

Below is an example using a Docker Compose stack with Traefik. It exposes the WireGuard Portal on https://wg.domain.com and redirects initial HTTP traffic to HTTPS.

services:\n  reverse-proxy:\n    image: traefik:v3.3\n    restart: unless-stopped\n    command:\n      #- '--log.level=DEBUG'\n      - '--providers.docker.endpoint=unix:///var/run/docker.sock'\n      - '--providers.docker.exposedbydefault=false'\n      - '--entrypoints.web.address=:80'\n      - '--entrypoints.websecure.address=:443'\n      - '--entrypoints.websecure.http3'\n      - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true'\n      - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web'\n      - '--certificatesresolvers.letsencryptresolver.acme.email=your.email@domain.com'\n      - '--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json'\n      #- '--certificatesresolvers.letsencryptresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory'  # just for testing\n    ports:\n      - 80:80 # for HTTP\n      - 443:443/tcp  # for HTTPS\n      - 443:443/udp  # for HTTP/3\n    volumes:\n      - acme-certs:/letsencrypt\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n    labels:\n      - 'traefik.enable=true'\n      # HTTP Catchall for redirecting HTTP -> HTTPS\n      - 'traefik.http.routers.dashboard-catchall.rule=Host(`wg.domain.com`) && PathPrefix(`/`)'\n      - 'traefik.http.routers.dashboard-catchall.entrypoints=web'\n      - 'traefik.http.routers.dashboard-catchall.middlewares=redirect-to-https'\n      - 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'\n\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    restart: unless-stopped\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      # WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)\n      - \"51820:51820/udp\"\n      # Web UI port (only available on localhost, Traefik will handle the HTTPS)\n      - \"127.0.0.1:8888:8888/tcp\"\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n    volumes:\n      # host path : container path\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n    labels:\n      - 'traefik.enable=true'\n      - 'traefik.http.routers.wgportal.rule=Host(`wg.domain.com`)'\n      - 'traefik.http.routers.wgportal.entrypoints=websecure'\n      - 'traefik.http.routers.wgportal.tls.certresolver=letsencryptresolver'\n      - 'traefik.http.routers.wgportal.service=wgportal'\n      - 'traefik.http.services.wgportal.loadbalancer.server.port=8888'\n\nvolumes:\n  acme-certs:\n

The WireGuard Portal configuration must be updated accordingly so that the correct external URL is set for the web interface:

web:\n  external_url: https://wg.domain.com\n
"},{"location":"documentation/getting-started/reverse-proxy/#built-in-tls","title":"Built-in TLS","text":"

If you prefer to let WireGuard Portal handle TLS itself, you can use the built-in TLS support. In your config.yaml, under the web section, point to your certificate and key files:

web:\n  cert_file: /path/to/your/fullchain.pem\n  key_file:  /path/to/your/privkey.pem\n

The web server will then use these files to serve HTTPS traffic directly instead of HTTP.

"},{"location":"documentation/getting-started/sources/","title":"Sources","text":"

To build the application from source files, use the Makefile provided in the repository.

"},{"location":"documentation/getting-started/sources/#requirements","title":"Requirements","text":"
  • Git
  • Make
  • Go: >=1.24.0
  • Node.js with npm: node>=18, npm>=9
"},{"location":"documentation/getting-started/sources/#build","title":"Build","text":"
# Get source code\ngit clone https://github.com/h44z/wg-portal -b ${WG_PORTAL_VERSION:-master} --depth 1\ncd wg-portal\n# Build the frontend\nmake frontend\n# Build the backend\nmake build\n
"},{"location":"documentation/getting-started/sources/#install","title":"Install","text":"

Compiled binary will be available in ./dist directory.

For installation instructions, check the Binaries section.

"},{"location":"documentation/monitoring/prometheus/","title":"Monitoring","text":"

By default, WG-Portal exposes Prometheus metrics on port 8787 if interface/peer statistic data collection is enabled.

"},{"location":"documentation/monitoring/prometheus/#exposed-metrics","title":"Exposed Metrics","text":"Metric Type Description wireguard_interface_received_bytes_total gauge Bytes received through the interface. wireguard_interface_sent_bytes_total gauge Bytes sent through the interface. wireguard_peer_last_handshake_seconds gauge Seconds from the last handshake with the peer. wireguard_peer_received_bytes_total gauge Bytes received from the peer. wireguard_peer_sent_bytes_total gauge Bytes sent to the peer. wireguard_peer_up gauge Peer connection state (boolean: 1/0)."},{"location":"documentation/monitoring/prometheus/#prometheus-config","title":"Prometheus Config","text":"

Add the following scrape job to your Prometheus config file:

# prometheus.yaml\nscrape_configs:\n  - job_name: wg-portal\n    scrape_interval: 60s\n    static_configs:\n      - targets:\n          - localhost:8787 # Change localhost to IP Address or hostname with WG-Portal\n
"},{"location":"documentation/monitoring/prometheus/#grafana-dashboard","title":"Grafana Dashboard","text":"

You may import dashboard.json into your Grafana instance.

"},{"location":"documentation/rest-api/api-doc/","title":"REST API","text":""},{"location":"documentation/upgrade/v1/","title":"Upgrade","text":"

Major upgrades between different versions may require special procedures, which are described in the following sections.

"},{"location":"documentation/upgrade/v1/#upgrade-from-v1-to-v2","title":"Upgrade from v1 to v2","text":"

Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!

To start the upgrade process, start the wg-portal binary with the -migrateFrom parameter. The configuration (config.yaml) for WireGuard Portal must be updated and valid before starting the upgrade.

To upgrade from a previous SQLite database, start wg-portal like:

./wg-portal-amd64 -migrateFrom=old_wg_portal.db\n

You can also specify the database type using the parameter -migrateFromType. Supported database types: mysql, mssql, postgres or sqlite.

For example:

./wg-portal-amd64 -migrateFromType=mysql -migrateFrom='user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local'\n

The upgrade will transform the old, existing database and store the values in the new database specified in the config.yaml configuration file. Ensure that the new database does not contain any data!

If you are using Docker, you can adapt the docker-compose.yml file to start the upgrade process:

services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    # ... other settings\n    restart: no\n    command: [\"-migrateFrom=/app/data/old_wg_portal.db\"]\n
"},{"location":"documentation/usage/backends/","title":"Backends","text":"

WireGuard Portal can manage WireGuard interfaces and peers on different backends. Each backend represents a system where interfaces actually live. You can register multiple backends and choose which one to use per interface. A global default backend determines where newly created interfaces go (unless you explicitly choose another in the UI).

Supported backends: - Local (default): Manages interfaces on the host running WireGuard Portal (Linux WireGuard via wgctrl). Use this when the portal should directly configure wg devices on the same server. - MikroTik RouterOS (beta): Manages interfaces and peers on MikroTik devices via the RouterOS REST API. Use this to control WG interfaces on RouterOS v7+.

How backend selection works: - The default backend is configured at backend.default (local or the id of a defined MikroTik backend). New interfaces created in the UI will use this backend by default. - Each interface stores its backend. You can select a different backend when creating a new interface.

"},{"location":"documentation/usage/backends/#configuring-mikrotik-backends-routeros-v7","title":"Configuring MikroTik backends (RouterOS v7+)","text":"

The MikroTik backend is currently marked beta. While basic functionality is implemented, some advanced features are not yet implemented or contain bugs. Please test carefully before using in production.

The MikroTik backend uses the REST API under a base URL ending with /rest. You can register one or more MikroTik devices as backends for a single WireGuard Portal instance.

"},{"location":"documentation/usage/backends/#prerequisites-on-mikrotik","title":"Prerequisites on MikroTik:","text":"
  • RouterOS v7 with WireGuard support.
  • REST API enabled and reachable over HTTP(S). A typical base URL is https://:8729/rest or https:///rest depending on your service setup.
  • A dedicated RouterOS user with the following group permissions:
  • api (for logging in via REST API)
  • rest-api (for logging in via REST API)
  • read (to read interface and peer data)
  • write (to create/update interfaces and peers)
  • test (to perform ping checks)
  • sensitive (to read private keys)
  • TLS certificate on the device is recommended. If you use a self-signed certificate during testing, set api_verify_tls: false in wg-portal (not recommended for production).
  • Example WireGuard Portal configuration (config/config.yaml):

    backend:\n  # default backend decides where new interfaces are created\n  default: mikrotik-prod\n\n  mikrotik:\n    - id: mikrotik-prod              # unique id, not \"local\"\n      display_name: RouterOS RB5009  # optional nice name\n      api_url: https://10.10.10.10/rest\n      api_user: wgportal\n      api_password: a-super-secret-password\n      api_verify_tls: true         # set to false only if using self-signed during testing\n      api_timeout: 30s             # maximum request duration\n      concurrency: 5               # limit parallel REST calls to device\n      debug: false                 # verbose logging for this backend\n
    "},{"location":"documentation/usage/backends/#known-limitations","title":"Known limitations:","text":"
    • The MikroTik backend is still in beta. Some features may not work as expected.
    • Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks)
    "},{"location":"documentation/usage/general/","title":"General","text":"

    This documentation section describes the general usage of WireGuard Portal. If you are looking for specific setup instructions, please refer to the Getting Started and Configuration sections, for example, using a Docker deployment.

    "},{"location":"documentation/usage/general/#basic-concepts","title":"Basic Concepts","text":"

    WireGuard Portal is a web-based configuration portal for WireGuard server management. It allows managing multiple WireGuard interfaces and users from a single web UI. WireGuard Interfaces can be categorized into three types:

    • Server: A WireGuard server interface that to which multiple peers can connect. In this mode, it is possible to specify default settings for all peers, such as the IP address range, DNS servers, and MTU size.
    • Client: A WireGuard client interface that can be used to connect to a WireGuard server. Usually, such an interface has exactly one peer.
    • Unknown: This is the default type for imported interfaces. It is encouraged to change the type to either Server or Client after importing the interface.
    "},{"location":"documentation/usage/general/#accessing-the-web-ui","title":"Accessing the Web UI","text":"

    The web UI should be accessed via the URL specified in the external_url property of the configuration file. By default, WireGuard Portal listens on port 8888 for HTTP connections. Check the Security section for more information on securing the web UI.

    So the default URL to access the web UI is:

    http://localhost:8888\n

    A freshly set-up WireGuard Portal instance will have a default admin user with the username admin@wgportal.local and the password wgportal-default. You can and should override the default credentials in the configuration file. Make sure to change the default password immediately after the first login!

    "},{"location":"documentation/usage/general/#basic-ui-description","title":"Basic UI Description","text":"

    As seen in the screenshot above, the web UI is divided into several sections which are accessible via the navigation bar on the top of the screen.

    1. Home: The landing page of WireGuard Portal. It provides a staring point for the user to access the different sections of the web UI. It also provides quick links to WireGuard Client downloads or official documentation.
    2. Interfaces: This section allows you to manage the WireGuard interfaces. You can add, edit, or delete interfaces, as well as view their status and statistics. Peers for each interface can be managed here as well.
    3. Users: This section allows you to manage the users of WireGuard Portal. You can add, edit, or delete users, as well as view their status and statistics.
    4. Key Generator: This section allows you to generate WireGuard keys locally on your browser. The generated keys are never sent to the server. This is useful if you want to generate keys for a new peer without having to store the private keys in the database.
    5. Profile / Settings: This section allows you to access your own profile page, settings, and audit logs.
    "},{"location":"documentation/usage/general/#interface-view","title":"Interface View","text":"

    The interface view provides an overview of the WireGuard interfaces and peers configured in WireGuard Portal.

    The most important elements are:

    1. Interface Selector: This dropdown allows you to select the WireGuard interface you want to manage. All further actions will be performed on the selected interface.
    2. Create new Interface: This button allows you to create a new WireGuard interface.
    3. Interface Overview: This section provides an overview of the selected WireGuard interface. It shows the interface type, number of peers, and other important information.
    4. List of Peers: This section provides a list of all peers associated with the selected WireGuard interface. You can view, add, edit, or delete peers from this list.
    5. Add new Peer: This button allows you to add a new peer to the selected WireGuard interface.
    6. Add multiple Peers: This button allows you to add multiple peers to the selected WireGuard interface. This is useful if you want to add a large number of peers at once.
    "},{"location":"documentation/usage/ldap/","title":"LDAP","text":"

    WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. You can even register multiple LDAP servers side-by-side. When someone logs in via LDAP, their specific provider is remembered, so there's no risk of cross-provider conflicts. Details on the log-in process can be found in the Security documentation.

    If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. The synchronization process can be fine-tuned by multiple parameters, which are described below.

    "},{"location":"documentation/usage/ldap/#ldap-synchronization","title":"LDAP Synchronization","text":"

    WireGuard Portal can automatically synchronize users from LDAP to the database. To enable this feature, set the sync_interval property in the LDAP provider configuration to a value greater than \"0\". The value is a string representing a duration, such as \"15m\" for 15 minutes or \"1h\" for 1 hour (check the exact format definition for details). The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. Also make sure that the sync_filter property is a well-formed LDAP filter, or synchronization will fail.

    "},{"location":"documentation/usage/ldap/#limiting-synchronization-to-specific-users","title":"Limiting Synchronization to Specific Users","text":"

    Use the sync_filter property in your LDAP provider block to restrict which users get synchronized. It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database.

    For example, to import only users with a mail attribute:

    auth:\n  ldap:\n    - id: ldap\n      # ... other settings\n      sync_filter: (mail=*)\n

    "},{"location":"documentation/usage/ldap/#disable-missing-users","title":"Disable Missing Users","text":"

    If you set the disable_missing property to true, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. All peers associated with that user will also be disabled.

    If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the auto_re_enable property to true. This will only re-enable the user if they where disabled by the synchronization process. Manually disabled users will not be re-enabled.

    "},{"location":"documentation/usage/security/","title":"Security","text":"

    This section describes the security features available to administrators for hardening WireGuard Portal and protecting its data.

    "},{"location":"documentation/usage/security/#authentication","title":"Authentication","text":"

    WireGuard Portal supports multiple authentication methods, including:

    • Local user accounts
    • LDAP authentication
    • OAuth and OIDC authentication
    • Passkey authentication (WebAuthn)

    Users can have two roles which limit their permissions in WireGuard Portal:

    • User: Can manage their own account and peers.
    • Admin: Can manage all users and peers, including the ability to manage WireGuard interfaces.
    "},{"location":"documentation/usage/security/#password-security","title":"Password Security","text":"

    WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. Local users are stored in the database, while LDAP users are authenticated against an external LDAP server.

    On initial startup, WireGuard Portal automatically creates a local admin account with the password wgportal-default.

    This password must be changed immediately after the first login.

    The minimum password length for all local users can be configured in the auth section of the configuration file. The default value is 16 characters, see min_password_length. The minimum password length is also enforced for the default admin user.

    "},{"location":"documentation/usage/security/#passkey-webauthn-authentication","title":"Passkey (WebAuthn) Authentication","text":"

    Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. This feature is enabled by default and can be configured in the webauthn section of the configuration file.

    Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked.

    Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback).

    To register a Passkey, open the settings page (1) in the web UI and click on the \"Register Passkey\" (2) button.

    "},{"location":"documentation/usage/security/#oauth-and-oidc-authentication","title":"OAuth and OIDC Authentication","text":"

    WireGuard Portal supports OAuth and OIDC authentication. You can use any OAuth or OIDC provider that supports the authorization code flow, such as Google, GitHub, or Keycloak.

    For OAuth or OIDC to work, you need to configure the external_url property in the web section of the configuration file. If you are planning to expose the portal to the internet, make sure that the external_url is configured to use HTTPS.

    To add OIDC or OAuth authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth provider and configure a new authentication provider in the auth section of the configuration file. Make sure that each configured provider has a unique provider_name property set. Samples can be seen here.

    "},{"location":"documentation/usage/security/#limiting-login-to-specific-domains","title":"Limiting Login to Specific Domains","text":"

    You can limit the login to specific domains by setting the allowed_domains property for OAuth or OIDC providers. This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. For example, if you want to allow only users with an email address ending in outlook.com to log in, set the property as follows:

    auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      allowed_domains:\n        - \"outlook.com\"\n
    "},{"location":"documentation/usage/security/#limit-login-to-existing-users","title":"Limit Login to Existing Users","text":"

    You can limit the login to existing users only by setting the registration_enabled property to false for OAuth or OIDC providers. If registration is enabled, new users will be created in the database when they log in for the first time.

    "},{"location":"documentation/usage/security/#admin-mapping","title":"Admin Mapping","text":"

    You can map users to admin roles based on their attributes in the OAuth or OIDC provider. To do this, set the admin_mapping property for the provider. Administrative access can either be mapped by a specific attribute or by group membership.

    Attribute specific mapping can be achieved by setting the admin_value_regex and the is_admin property. The admin_value_regex property is a regular expression that is matched against the value of the is_admin attribute. The user is granted admin access if the regex matches the attribute value.

    Example:

    auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      field_map:\n        is_admin: \"wg_admin_prop\"\n      admin_mapping:\n        admin_value_regex: \"^true$\"\n
    The example above will grant admin access to users with the wg_admin_prop attribute set to true.

    Group membership mapping can be achieved by setting the admin_group_regex and user_groups property. The admin_group_regex property is a regular expression that is matched against the group names of the user. The user is granted admin access if the regex matches any of the group names.

    Example:

    auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      field_map:\n        user_groups: \"groups\"\n      admin_mapping:\n        admin_group_regex: \"^the-admin-group$\"\n
    The example above will grant admin access to users who are members of the the-admin-group group.

    "},{"location":"documentation/usage/security/#ldap-authentication","title":"LDAP Authentication","text":"

    WireGuard Portal supports LDAP authentication. You can use any LDAP server that supports the LDAP protocol, such as Active Directory or OpenLDAP. Multiple LDAP servers can be configured in the auth section of the configuration file. WireGuard Portal remembers the authentication provider of the user and therefore avoids conflicts between multiple LDAP providers.

    To configure LDAP authentication, create a new ldap authentication provider in the auth section of the configuration file.

    "},{"location":"documentation/usage/security/#limiting-login-to-specific-users","title":"Limiting Login to Specific Users","text":"

    You can limit the login to specific users by setting the login_filter property for LDAP provider. This filter uses the LDAP search filter syntax. The username can be inserted into the query by placing the {{login_identifier}} placeholder in the filter. This placeholder will then be replaced with the username entered by the user during login.

    For example, if you want to allow only users with the objectClass attribute set to organizationalPerson to log in, set the property as follows:

    auth:\n  ldap:\n    - provider_name: \"ldap1\"\n      # ... other settings\n      login_filter: \"(&(objectClass=organizationalPerson)(uid={{login_identifier}}))\"\n

    The login_filter should always be designed to return at most one user.

    "},{"location":"documentation/usage/security/#limit-login-to-existing-users_1","title":"Limit Login to Existing Users","text":"

    You can limit the login to existing users only by setting the registration_enabled property to false for LDAP providers. If registration is enabled, new users will be created in the database when they log in for the first time.

    "},{"location":"documentation/usage/security/#admin-mapping_1","title":"Admin Mapping","text":"

    You can map users to admin roles based on their group membership in the LDAP server. To do this, set the admin_group and memberof property for the provider. The admin_group property defines the distinguished name of the group that is allowed to log in as admin. All groups that are listed in the memberof attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access.

    "},{"location":"documentation/usage/security/#ui-and-api-access","title":"UI and API Access","text":"

    WireGuard Portal provides a web UI and a REST API for user interaction. It is important to secure these interfaces to prevent unauthorized access and data breaches.

    "},{"location":"documentation/usage/security/#https","title":"HTTPS","text":"

    It is recommended to use HTTPS for all communication with the portal to prevent eavesdropping.

    Event though, WireGuard Portal supports HTTPS out of the box, it is recommended to use a reverse proxy like Nginx or Traefik to handle SSL termination and other security features. A detailed explanation is available in the Reverse Proxy section.

    "},{"location":"documentation/usage/webhooks/","title":"Webhooks","text":"

    Webhooks allow WireGuard Portal to notify external services about events such as user creation, device changes, or configuration updates. This enables integration with other systems and automation workflows.

    When webhooks are configured and a specified event occurs, WireGuard Portal sends an HTTP POST request to the configured webhook URL. The payload contains event-specific data in JSON format.

    "},{"location":"documentation/usage/webhooks/#configuration","title":"Configuration","text":"

    All available configuration options for webhooks can be found in the configuration overview.

    A basic webhook configuration looks like this:

    webhook:\n  url: https://your-service.example.com/webhook\n
    "},{"location":"documentation/usage/webhooks/#security","title":"Security","text":"

    Webhooks can be secured by using a shared secret. This secret is included in the Authorization header of the webhook request, allowing your service to verify the authenticity of the request. You can set the shared secret in the webhook configuration:

    webhook:\n  url: https://your-service.example.com/webhook\n  secret: \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"\n

    You should also make sure that your webhook endpoint is secured with HTTPS to prevent eavesdropping and tampering.

    "},{"location":"documentation/usage/webhooks/#available-events","title":"Available Events","text":"

    WireGuard Portal supports various events that can trigger webhooks. The following events are available:

    • create: Triggered when a new entity is created.
    • update: Triggered when an existing entity is updated.
    • delete: Triggered when an entity is deleted.
    • connect: Triggered when a user connects to the VPN.
    • disconnect: Triggered when a user disconnects from the VPN.

    The following entity models are supported for webhook events:

    • user: WireGuard Portal users support creation, update, or deletion events.
    • peer: Peers support creation, update, or deletion events. Via the peer_metric entity, you can also receive connection status updates.
    • peer_metric: Peer metrics support connection status updates, such as when a peer connects or disconnects.
    • interface: WireGuard interfaces support creation, update, or deletion events.
    "},{"location":"documentation/usage/webhooks/#payload-structure","title":"Payload Structure","text":"

    All webhook events send a JSON payload containing relevant data. The structure of the payload depends on the event type and entity involved. A common shell structure for webhook payloads is as follows:

    {\n  \"event\": \"create\", // The event type, e.g. \"create\", \"update\", \"delete\", \"connect\", \"disconnect\"\n  \"entity\": \"user\",  // The entity type, e.g. \"user\", \"peer\", \"peer_metric\", \"interface\"\n  \"identifier\": \"the-user-identifier\", // Unique identifier of the entity, e.g. user ID or peer ID\n  \"payload\": {\n    // The payload of the event, e.g. a Peer model.\n    // Detailed model descriptions are provided below.\n  }\n}\n
    "},{"location":"documentation/usage/webhooks/#payload-models","title":"Payload Models","text":"

    All payload models are encoded as JSON objects. Fields with empty values might be omitted in the payload.

    "},{"location":"documentation/usage/webhooks/#user-payload-entity-user","title":"User Payload (entity: user)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Time of creation UpdatedAt time.Time Time of last update Identifier string Unique user identifier Email string User email Source string Authentication source ProviderName string Name of auth provider IsAdmin bool Whether user has admin privileges Firstname string User's first name (optional) Lastname string User's last name (optional) Phone string Contact phone number (optional) Department string User's department (optional) Notes string Additional notes (optional) Disabled *time.Time When user was disabled DisabledReason string Reason for deactivation Locked *time.Time When user account was locked LockedReason string Reason for being locked"},{"location":"documentation/usage/webhooks/#peer-payload-entity-peer","title":"Peer Payload (entity: peer)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Creation timestamp UpdatedAt time.Time Last update timestamp Endpoint string Peer endpoint address EndpointPublicKey string Public key of peer endpoint AllowedIPsStr string Allowed IPs ExtraAllowedIPsStr string Extra allowed IPs PresharedKey string Pre-shared key for encryption PersistentKeepalive int Keepalive interval in seconds DisplayName string Display name of the peer Identifier string Unique identifier UserIdentifier string Associated user ID (optional) InterfaceIdentifier string Interface this peer is attached to Disabled *time.Time When the peer was disabled DisabledReason string Reason for being disabled ExpiresAt *time.Time Expiration date Notes string Notes for this peer AutomaticallyCreated bool Whether peer was auto-generated PrivateKey string Peer private key PublicKey string Peer public key InterfaceType string Type of the peer interface Addresses []string IP addresses CheckAliveAddress string Address used for alive checks DnsStr string DNS servers DnsSearchStr string DNS search domains Mtu int MTU (Maximum Transmission Unit) FirewallMark uint32 Firewall mark (optional) RoutingTable string Custom routing table (optional) PreUp string Command before bringing up interface PostUp string Command after bringing up interface PreDown string Command before bringing down interface PostDown string Command after bringing down interface"},{"location":"documentation/usage/webhooks/#interface-payload-entity-interface","title":"Interface Payload (entity: interface)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Creation timestamp UpdatedAt time.Time Last update timestamp Identifier string Unique identifier PrivateKey string Private key for the interface PublicKey string Public key for the interface ListenPort int Listening port Addresses []string IP addresses DnsStr string DNS servers DnsSearchStr string DNS search domains Mtu int MTU (Maximum Transmission Unit) FirewallMark uint32 Firewall mark RoutingTable string Custom routing table PreUp string Command before bringing up interface PostUp string Command after bringing up interface PreDown string Command before bringing down interface PostDown string Command after bringing down interface SaveConfig bool Whether to save config to file DisplayName string Human-readable name Type string Type of interface DriverType string Driver used Disabled *time.Time When the interface was disabled DisabledReason string Reason for being disabled PeerDefNetworkStr string Default peer network configuration PeerDefDnsStr string Default peer DNS servers PeerDefDnsSearchStr string Default peer DNS search domains PeerDefEndpoint string Default peer endpoint PeerDefAllowedIPsStr string Default peer allowed IPs PeerDefMtu int Default peer MTU PeerDefPersistentKeepalive int Default keepalive value PeerDefFirewallMark uint32 Default firewall mark for peers PeerDefRoutingTable string Default routing table for peers PeerDefPreUp string Default peer pre-up command PeerDefPostUp string Default peer post-up command PeerDefPreDown string Default peer pre-down command PeerDefPostDown string Default peer post-down command"},{"location":"documentation/usage/webhooks/#peer-metrics-payload-entity-peer_metric","title":"Peer Metrics Payload (entity: peer_metric)","text":"JSON Field Type Description Status PeerStatus Current status of the peer Peer Peer Peer data

    PeerStatus sub-structure:

    JSON Field Type Description UpdatedAt time.Time Time of last status update IsConnected bool Is peer currently connected IsPingable bool Can peer be pinged LastPing *time.Time Time of last successful ping BytesReceived uint64 Bytes received from peer BytesTransmitted uint64 Bytes sent to peer Endpoint string Last known endpoint LastHandshake *time.Time Last successful handshake LastSessionStart *time.Time Time the last session began"},{"location":"documentation/usage/webhooks/#example-payloads","title":"Example Payloads","text":"

    The following payload is an example of a webhook event when a peer connects to the VPN:

    {\n  \"event\": \"connect\",\n  \"entity\": \"peer_metric\",\n  \"identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n  \"payload\": {\n    \"Status\": {\n      \"UpdatedAt\": \"2025-06-27T22:20:08.734900034+02:00\",\n      \"IsConnected\": true,\n      \"IsPingable\": false,\n      \"BytesReceived\": 212,\n      \"BytesTransmitted\": 2884,\n      \"Endpoint\": \"10.55.66.77:58756\",\n      \"LastHandshake\": \"2025-06-27T22:19:46.580842776+02:00\",\n      \"LastSessionStart\": \"2025-06-27T22:19:46.580842776+02:00\"\n    },\n    \"Peer\": {\n      \"CreatedBy\": \"admin@wgportal.local\",\n      \"UpdatedBy\": \"admin@wgportal.local\",\n      \"CreatedAt\": \"2025-06-26T21:43:49.251839574+02:00\",\n      \"UpdatedAt\": \"2025-06-27T22:18:39.67763985+02:00\",\n      \"Endpoint\": \"10.55.66.1:51820\",\n      \"EndpointPublicKey\": \"eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=\",\n      \"AllowedIPsStr\": \"10.11.12.0/24,fdfd:d3ad:c0de:1234::/64\",\n      \"ExtraAllowedIPsStr\": \"\",\n      \"PresharedKey\": \"p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=\",\n      \"PersistentKeepalive\": 16,\n      \"DisplayName\": \"Peer Fb5TaziA\",\n      \"Identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n      \"UserIdentifier\": \"admin@wgportal.local\",\n      \"InterfaceIdentifier\": \"wgTesting\",\n      \"AutomaticallyCreated\": false,\n      \"PrivateKey\": \"QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=\",\n      \"PublicKey\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n      \"InterfaceType\": \"client\",\n      \"Addresses\": [\n        \"10.11.12.10/32\",\n        \"fdfd:d3ad:c0de:1234::a/128\"\n      ],\n      \"CheckAliveAddress\": \"\",\n      \"DnsStr\": \"\",\n      \"DnsSearchStr\": \"\",\n      \"Mtu\": 1420\n    }\n  }\n}\n

    Here is another example of a webhook event when a peer is updated:

    {\n  \"event\": \"update\",\n  \"entity\": \"peer\",\n  \"identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n  \"payload\": {\n    \"CreatedBy\": \"admin@wgportal.local\",\n    \"UpdatedBy\": \"admin@wgportal.local\",\n    \"CreatedAt\": \"2025-06-26T21:43:49.251839574+02:00\",\n    \"UpdatedAt\": \"2025-06-27T22:18:39.67763985+02:00\",\n    \"Endpoint\": \"10.55.66.1:51820\",\n    \"EndpointPublicKey\": \"eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=\",\n    \"AllowedIPsStr\": \"10.11.12.0/24,fdfd:d3ad:c0de:1234::/64\",\n    \"ExtraAllowedIPsStr\": \"\",\n    \"PresharedKey\": \"p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=\",\n    \"PersistentKeepalive\": 16,\n    \"DisplayName\": \"Peer Fb5TaziA\",\n    \"Identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n    \"UserIdentifier\": \"admin@wgportal.local\",\n    \"InterfaceIdentifier\": \"wgTesting\",\n    \"AutomaticallyCreated\": false,\n    \"PrivateKey\": \"QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=\",\n    \"PublicKey\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n    \"InterfaceType\": \"client\",\n    \"Addresses\": [\n      \"10.11.12.10/32\",\n      \"fdfd:d3ad:c0de:1234::a/128\"\n    ],\n    \"CheckAliveAddress\": \"\",\n    \"DnsStr\": \"\",\n    \"DnsSearchStr\": \"\",\n    \"Mtu\": 1420\n  }\n}\n
    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"documentation/overview/","title":"Overview","text":"

    WireGuard Portal is a simple, web-based configuration portal for WireGuard server management. The portal uses the WireGuard wgctrl library to manage existing VPN interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN connections.

    The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.

    "},{"location":"documentation/overview/#features","title":"Features","text":"
    • Self-hosted - the whole application is a single binary
    • Responsive multi-language web UI with dark-mode written in Vue.js
    • Automatically selects IP from the network pool assigned to the client
    • QR-Code for convenient mobile client configuration
    • Sends email to the client with QR-code and client config
    • Enable / Disable clients seamlessly
    • Generation of wg-quick configuration file (wgX.conf) if required
    • User authentication (database, OAuth, or LDAP), Passkey support
    • IPv6 ready
    • Docker ready
    • Can be used with existing WireGuard setups
    • Support for multiple WireGuard interfaces
    • Supports multiple WireGuard backends (wgctrl or MikroTik)
    • Peer Expiry Feature
    • Handles route and DNS settings like wg-quick does
    • Exposes Prometheus metrics for monitoring and alerting
    • REST API for management and client deployment
    • Webhook for custom actions on peer, interface, or user updates
    "},{"location":"documentation/configuration/examples/","title":"Examples","text":"

    Below are some sample YAML configurations demonstrating how to override some default values.

    "},{"location":"documentation/configuration/examples/#basic","title":"Basic","text":"
    core:\n  admin_user: test@example.com\n  admin_password: password\n  admin_api_token: super-s3cr3t-api-token-or-a-UUID\n  import_existing: false\n  create_default_peer: true\n  self_provisioning_allowed: true\n\nbackend:\n  # default backend decides where new interfaces are created\n  default: mikrotik\n\n  mikrotik:\n    - id: mikrotik                   # unique id, not \"local\"\n      display_name: RouterOS RB5009  # optional nice name\n      api_url: https://10.10.10.10/rest\n      api_user: wgportal\n      api_password: a-super-secret-password\n      api_verify_tls: false        # set to false only if using self-signed during testing\n      api_timeout: 30s             # maximum request duration\n      concurrency: 5               # limit parallel REST calls to device\n      debug: false                 # verbose logging for this backend\n      ignored_interfaces:          # ignore these interfaces during import\n      - wgTest1\n      - wgTest2\n\nweb:\n  site_title: My WireGuard Server\n  site_company_name: My Company\n  listening_address: :8080\n  external_url: https://my.external-domain.com\n  csrf_secret: super-s3cr3t-csrf\n  session_secret: super-s3cr3t-session\n  request_logging: true\n\nadvanced:\n  log_level: trace\n  log_pretty: true\n  log_json: false\n  config_storage_path: /etc/wireguard\n  expiry_check_interval: 5m\n\ndatabase:\n  debug: true\n  type: sqlite\n  dsn: data/sqlite.db\n  encryption_passphrase: change-this-s3cr3t-encryption-passphrase\n\nauth:\n  webauthn:\n    enabled: true\n
    "},{"location":"documentation/configuration/examples/#ldap-authentication-and-synchronization","title":"LDAP Authentication and Synchronization","text":"
    # ... (basic configuration)\n\nauth:\n  ldap:\n    # a sample LDAP provider with user sync enabled\n    - id: ldap\n      provider_name: Active Directory\n      url: ldap://srv-ad1.company.local:389\n      bind_user: ldap_wireguard@company.local\n      bind_pass: super-s3cr3t-ldap\n      base_dn: DC=COMPANY,DC=LOCAL\n      login_filter: (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))\n      sync_interval: 15m\n      sync_filter: (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))\n      disable_missing: true\n      field_map:\n        user_identifier: sAMAccountName\n        email: mail\n        firstname: givenName\n        lastname: sn\n        phone: telephoneNumber\n        department: department\n        memberof: memberOf\n      admin_group: CN=WireGuardAdmins,OU=Some-OU,DC=COMPANY,DC=LOCAL\n      registration_enabled: true\n      log_user_info: true\n
    "},{"location":"documentation/configuration/examples/#openid-connect-oidc-authentication","title":"OpenID Connect (OIDC) Authentication","text":"
    # ... (basic configuration)\n\nauth:\n  oidc:\n    # A sample Entra ID provider with environment variable substitution.\n    # Only users with an @outlook.com email address are allowed to register or login.\n    - id: azure\n      provider_name: azure\n      display_name: Login with</br>Entra ID\n      registration_enabled: true\n      base_url: \"https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0\"\n      client_id: \"${AZURE_CLIENT_ID}\"\n      client_secret: \"${AZURE_CLIENT_SECRET}\"\n      allowed_domains:\n        - \"outlook.com\"\n      extra_scopes:\n        - profile\n        - email\n\n    # a sample provider where users with the attribute `wg_admin` set to `true` are considered as admins\n    - id: oidc-with-admin-attribute\n      provider_name: google\n      display_name: Login with</br>Google\n      base_url: https://accounts.google.com\n      client_id: the-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      extra_scopes:\n        - https://www.googleapis.com/auth/userinfo.email\n        - https://www.googleapis.com/auth/userinfo.profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: given_name\n        lastname: family_name\n        phone: phone_number\n        department: department\n        is_admin: wg_admin\n      admin_mapping:\n        admin_value_regex: ^true$\n      registration_enabled: true\n      log_user_info: true\n\n    # a sample provider where users in the group `the-admin-group` are considered as admins\n    - id: oidc-with-admin-group\n      provider_name: google2\n      display_name: Login with</br>Google2\n      base_url: https://accounts.google.com\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      extra_scopes:\n        - https://www.googleapis.com/auth/userinfo.email\n        - https://www.googleapis.com/auth/userinfo.profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: given_name\n        lastname: family_name\n        phone: phone_number\n        department: department\n        user_groups: groups\n      admin_mapping:\n        admin_group_regex: ^the-admin-group$\n      registration_enabled: true\n      log_user_info: true\n
    "},{"location":"documentation/configuration/examples/#plain-oauth2-authentication","title":"Plain OAuth2 Authentication","text":"
    # ... (basic configuration)\n\nauth:\n  oauth:\n    # a sample provider where users with the attribute `this-attribute-must-be-true` set to `true` or `True`\n    # are considered as admins\n    - id: google_plain_oauth-with-admin-attribute\n      provider_name: google3\n      display_name: Login with</br>Google3\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      auth_url: https://accounts.google.com/o/oauth2/v2/auth\n      token_url: https://oauth2.googleapis.com/token\n      user_info_url: https://openidconnect.googleapis.com/v1/userinfo\n      scopes:\n        - openid\n        - email\n        - profile\n      field_map:\n        user_identifier: sub\n        email: email\n        firstname: name\n        is_admin: this-attribute-must-be-true\n      admin_mapping:\n        admin_value_regex: ^(True|true)$\n      registration_enabled: true\n\n    # a sample provider where either users with the attribute `this-attribute-must-be-true` set to `true` or \n    # users in the group `admin-group-name` are considered as admins\n    - id: google_plain_oauth_with_groups\n      provider_name: google4\n      display_name: Login with</br>Google4\n      client_id: another-client-id-1234.apps.googleusercontent.com\n      client_secret: A_CLIENT_SECRET\n      auth_url: https://accounts.google.com/o/oauth2/v2/auth\n      token_url: https://oauth2.googleapis.com/token\n      user_info_url: https://openidconnect.googleapis.com/v1/userinfo\n      scopes:\n        - openid\n        - email\n        - profile\n        - i-want-some-groups\n      field_map:\n        email: email\n        firstname: name\n        user_identifier: sub\n        is_admin: this-attribute-must-be-true\n        user_groups: groups\n      admin_mapping:\n        admin_value_regex: ^true$\n        admin_group_regex: ^admin-group-name$\n      registration_enabled: true\n      log_user_info: true\n

    For more information, check out the usage documentation (e.g. General Configuration or Backends Configuration).

    "},{"location":"documentation/configuration/overview/","title":"Overview","text":"

    This page provides an overview of all available configuration options for WireGuard Portal.

    You can supply these configurations in a YAML file when starting the Portal. The path of the configuration file defaults to config/config.yaml (or config/config.yml) in the working directory of the executable. It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG. For example: WG_PORTAL_CONFIG=/etc/wg-portal/config.yaml ./wg-portal. Also, environment variable substitution in the config file is supported. Refer to the syntax.

    Configuration examples are available on the Examples page.

    Default configuration
    core:\n  admin_user: admin@wgportal.local\n  admin_password: wgportal-default\n  admin_api_token: \"\"\n  disable_admin_user: false\n  editable_keys: true\n  create_default_peer: false\n  create_default_peer_on_creation: false\n  re_enable_peer_after_user_enable: true\n  delete_peer_after_user_deleted: false\n  self_provisioning_allowed: false\n  import_existing: true\n  restore_state: true\n\nbackend:\n  default: local\n\nadvanced:\n  log_level: info\n  log_pretty: false\n  log_json: false\n  start_listen_port: 51820\n  start_cidr_v4: 10.11.12.0/24\n  start_cidr_v6: fdfd:d3ad:c0de:1234::0/64\n  use_ip_v6: true\n  config_storage_path: \"\"\n  expiry_check_interval: 15m\n  rule_prio_offset: 20000\n  route_table_offset: 20000\n  api_admin_only: true\n  limit_additional_user_peers: 0\n\ndatabase:\n  debug: false\n  slow_query_threshold: \"0\"\n  type: sqlite\n  dsn: data/sqlite.db\n  encryption_passphrase: \"\"\n\nstatistics:\n  use_ping_checks: true\n  ping_check_workers: 10\n  ping_unprivileged: false\n  ping_check_interval: 1m\n  data_collection_interval: 1m\n  collect_interface_data: true\n  collect_peer_data: true\n  collect_audit_data: true\n  listening_address: :8787\n\nmail:\n  host: 127.0.0.1\n  port: 25\n  encryption: none\n  cert_validation: true\n  username: \"\"\n  password: \"\"\n  auth_type: plain\n  from: Wireguard Portal <noreply@wireguard.local>\n  link_only: false\n\nauth:\n  oidc: []\n  oauth: []\n  ldap: []\n  webauthn:\n    enabled: true\n  min_password_length: 16\n  hide_login_form: false\n\nweb:\n  listening_address: :8888\n  external_url: http://localhost:8888\n  site_company_name: WireGuard Portal\n  site_title: WireGuard Portal\n  session_identifier: wgPortalSession\n  session_secret: very_secret\n  csrf_secret: extremely_secret\n  request_logging: false\n  expose_host_info: false\n  cert_file: \"\"\n  key_File: \"\"\n\nwebhook:\n  url: \"\"\n  authentication: \"\"\n  timeout: 10s\n

    Below you will find sections like core, backend, advanced, database, statistics, mail, auth, web and webhook. Each section describes the individual configuration keys, their default values, and a brief explanation of their purpose.

    "},{"location":"documentation/configuration/overview/#core","title":"Core","text":"

    These are the primary configuration options that control fundamental WireGuard Portal behavior. More advanced options are found in the subsequent Advanced section.

    "},{"location":"documentation/configuration/overview/#admin_user","title":"admin_user","text":"
    • Default: admin@wgportal.local
    • Description: The administrator user. This user will be created as a default admin if it does not yet exist.
    "},{"location":"documentation/configuration/overview/#admin_password","title":"admin_password","text":"
    • Default: wgportal-default
    • Description: The administrator password. The default password should be changed immediately!
    • Important: The password should be strong and secure. The minimum password length is specified in auth.min_password_length. By default, it is 16 characters.
    "},{"location":"documentation/configuration/overview/#disable_admin_user","title":"disable_admin_user","text":"
    • Default: false
    • Description: If true, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth.
    "},{"location":"documentation/configuration/overview/#admin_api_token","title":"admin_api_token","text":"
    • Default: (empty)
    • Description: An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user.
    "},{"location":"documentation/configuration/overview/#editable_keys","title":"editable_keys","text":"
    • Default: true
    • Description: Allow editing of WireGuard key-pairs directly in the UI.
    "},{"location":"documentation/configuration/overview/#create_default_peer","title":"create_default_peer","text":"
    • Default: false
    • Description: If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces.
    "},{"location":"documentation/configuration/overview/#create_default_peer_on_creation","title":"create_default_peer_on_creation","text":"
    • Default: false
    • Description: If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces.
    "},{"location":"documentation/configuration/overview/#re_enable_peer_after_user_enable","title":"re_enable_peer_after_user_enable","text":"
    • Default: true
    • Description: Re-enable all peers that were previously disabled if the associated user is re-enabled.
    "},{"location":"documentation/configuration/overview/#delete_peer_after_user_deleted","title":"delete_peer_after_user_deleted","text":"
    • Default: false
    • Description: If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled.
    "},{"location":"documentation/configuration/overview/#self_provisioning_allowed","title":"self_provisioning_allowed","text":"
    • Default: false
    • Description: Allow registered (non-admin) users to self-provision peers from their profile page.
    "},{"location":"documentation/configuration/overview/#import_existing","title":"import_existing","text":"
    • Default: true
    • Description: On startup, import existing WireGuard interfaces and peers into WireGuard Portal.
    "},{"location":"documentation/configuration/overview/#restore_state","title":"restore_state","text":"
    • Default: true
    • Description: Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started.
    "},{"location":"documentation/configuration/overview/#backend","title":"Backend","text":"

    Configuration options for the WireGuard backend, which manages the WireGuard interfaces and peers. The current MikroTik backend is in BETA and may not support all features.

    "},{"location":"documentation/configuration/overview/#default","title":"default","text":"
    • Default: local
    • Description: The default backend to use for managing WireGuard interfaces. Valid options are: local, or other backend id's configured in the mikrotik section.
    "},{"location":"documentation/configuration/overview/#ignored_local_interfaces","title":"ignored_local_interfaces","text":"
    • Default: (empty)
    • Description: A list of interface names to exclude when enumerating local interfaces. This is useful if you want to prevent certain interfaces from being imported from the local system.
    "},{"location":"documentation/configuration/overview/#mikrotik","title":"Mikrotik","text":"

    The mikrotik array contains a list of MikroTik backend definitions. Each entry describes how to connect to a MikroTik RouterOS instance that hosts WireGuard interfaces.

    Below are the properties for each entry inside backend.mikrotik:

    "},{"location":"documentation/configuration/overview/#id","title":"id","text":"
    • Default: (empty)
    • Description: A unique identifier for this backend. This value can be referenced by backend.default to use this backend as default. The identifier must be unique across all backends and must not use the reserved keyword local.
    "},{"location":"documentation/configuration/overview/#display_name","title":"display_name","text":"
    • Default: (empty)
    • Description: A human-friendly display name for this backend. If omitted, the id will be used as the display name.
    "},{"location":"documentation/configuration/overview/#api_url","title":"api_url","text":"
    • Default: (empty)
    • Description: Base URL of the MikroTik REST API, including scheme and path, e.g., https://10.10.10.10:8729/rest.
    "},{"location":"documentation/configuration/overview/#api_user","title":"api_user","text":"
    • Default: (empty)
    • Description: Username for authenticating against the MikroTik API. Ensure that the user has sufficient permissions to manage WireGuard interfaces and peers.
    "},{"location":"documentation/configuration/overview/#api_password","title":"api_password","text":"
    • Default: (empty)
    • Description: Password for the specified API user.
    "},{"location":"documentation/configuration/overview/#api_verify_tls","title":"api_verify_tls","text":"
    • Default: false
    • Description: Whether to verify the TLS certificate of the MikroTik API endpoint. Set to false to allow self-signed certificates (not recommended for production).
    "},{"location":"documentation/configuration/overview/#api_timeout","title":"api_timeout","text":"
    • Default: 30s
    • Description: Timeout for API requests to the MikroTik device. Uses Go duration format (e.g., 10s, 1m). If omitted, a default of 30 seconds is used.
    "},{"location":"documentation/configuration/overview/#concurrency","title":"concurrency","text":"
    • Default: 5
    • Description: Maximum number of concurrent API requests the backend will issue when enumerating interfaces and their details. If 0 or negative, a sane default of 5 is used.
    "},{"location":"documentation/configuration/overview/#ignored_interfaces","title":"ignored_interfaces","text":"
    • Default: (empty)
    • Description: A list of interface names to exclude during interface enumeration. This is useful if you want to prevent specific interfaces from being imported from the MikroTik device.
    "},{"location":"documentation/configuration/overview/#debug","title":"debug","text":"
    • Default: false
    • Description: Enable verbose debug logging for the MikroTik backend.

    For more details on configuring the MikroTik backend, see the Backends documentation.

    "},{"location":"documentation/configuration/overview/#advanced","title":"Advanced","text":"

    Additional or more specialized configuration options for logging and interface creation details.

    "},{"location":"documentation/configuration/overview/#log_level","title":"log_level","text":"
    • Default: info
    • Description: The log level used by the application. Valid options are: trace, debug, info, warn, error.
    "},{"location":"documentation/configuration/overview/#log_pretty","title":"log_pretty","text":"
    • Default: false
    • Description: If true, log messages are colorized and formatted for readability (pretty-print).
    "},{"location":"documentation/configuration/overview/#log_json","title":"log_json","text":"
    • Default: false
    • Description: If true, log messages are structured in JSON format.
    "},{"location":"documentation/configuration/overview/#start_listen_port","title":"start_listen_port","text":"
    • Default: 51820
    • Description: The first port to use when automatically creating new WireGuard interfaces.
    "},{"location":"documentation/configuration/overview/#start_cidr_v4","title":"start_cidr_v4","text":"
    • Default: 10.11.12.0/24
    • Description: The initial IPv4 subnet to use when automatically creating new WireGuard interfaces.
    "},{"location":"documentation/configuration/overview/#start_cidr_v6","title":"start_cidr_v6","text":"
    • Default: fdfd:d3ad:c0de:1234::0/64
    • Description: The initial IPv6 subnet to use when automatically creating new WireGuard interfaces.
    "},{"location":"documentation/configuration/overview/#use_ip_v6","title":"use_ip_v6","text":"
    • Default: true
    • Description: Enable or disable IPv6 support.
    "},{"location":"documentation/configuration/overview/#config_storage_path","title":"config_storage_path","text":"
    • Default: (empty)
    • Description: Path to a directory where wg-quick style configuration files will be stored (if you need local filesystem configs).
    "},{"location":"documentation/configuration/overview/#expiry_check_interval","title":"expiry_check_interval","text":"
    • Default: 15m
    • Description: Interval after which existing peers are checked if they are expired. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
    "},{"location":"documentation/configuration/overview/#rule_prio_offset","title":"rule_prio_offset","text":"
    • Default: 20000
    • Description: Offset for IP route rule priorities when configuring routing.
    "},{"location":"documentation/configuration/overview/#route_table_offset","title":"route_table_offset","text":"
    • Default: 20000
    • Description: Offset for IP route table IDs when configuring routing.
    "},{"location":"documentation/configuration/overview/#api_admin_only","title":"api_admin_only","text":"
    • Default: true
    • Description: If true, the public REST API is accessible only to admin users. The API docs live at /api/v1/doc.html.
    "},{"location":"documentation/configuration/overview/#limit_additional_user_peers","title":"limit_additional_user_peers","text":"
    • Default: 0
    • Description: Limit additional peers a normal user can create. 0 means unlimited.
    "},{"location":"documentation/configuration/overview/#database","title":"Database","text":"

    Configuration for the underlying database used by WireGuard Portal. Supported databases include SQLite, MySQL, Microsoft SQL Server, and Postgres.

    If sensitive values (like private keys) should be stored in an encrypted format, set the encryption_passphrase option.

    "},{"location":"documentation/configuration/overview/#debug_1","title":"debug","text":"
    • Default: false
    • Description: If true, logs all database statements (verbose).
    "},{"location":"documentation/configuration/overview/#slow_query_threshold","title":"slow_query_threshold","text":"
    • Default: \"0\"
    • Description: A time threshold (e.g., 100ms) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses s, ms for seconds, milliseconds, see time.ParseDuration. The value must be a string.
    "},{"location":"documentation/configuration/overview/#type","title":"type","text":"
    • Default: sqlite
    • Description: The database type. Valid options: sqlite, mssql, mysql, postgres.
    "},{"location":"documentation/configuration/overview/#dsn","title":"dsn","text":"
    • Default: data/sqlite.db
    • Description: The Data Source Name (DSN) for connecting to the database. For example:
      user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local\n
    "},{"location":"documentation/configuration/overview/#encryption_passphrase","title":"encryption_passphrase","text":"
    • Default: (empty)
    • Description: Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set. Important: Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward. New or updated records will be encrypted; existing data remains in plaintext until it\u2019s next modified.
    "},{"location":"documentation/configuration/overview/#statistics","title":"Statistics","text":"

    Controls how WireGuard Portal collects and reports usage statistics, including ping checks and Prometheus metrics.

    "},{"location":"documentation/configuration/overview/#use_ping_checks","title":"use_ping_checks","text":"
    • Default: true
    • Description: Enable periodic ping checks to verify that peers remain responsive.
    "},{"location":"documentation/configuration/overview/#ping_check_workers","title":"ping_check_workers","text":"
    • Default: 10
    • Description: Number of parallel worker processes for ping checks.
    "},{"location":"documentation/configuration/overview/#ping_unprivileged","title":"ping_unprivileged","text":"
    • Default: false
    • Description: If false, ping checks run without root privileges. This is currently considered BETA.
    "},{"location":"documentation/configuration/overview/#ping_check_interval","title":"ping_check_interval","text":"
    • Default: 1m
    • Description: Interval between consecutive ping checks for all peers. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
    "},{"location":"documentation/configuration/overview/#data_collection_interval","title":"data_collection_interval","text":"
    • Default: 1m
    • Description: Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration.
    "},{"location":"documentation/configuration/overview/#collect_interface_data","title":"collect_interface_data","text":"
    • Default: true
    • Description: If true, collects interface-level data (bytes in/out) for monitoring and statistics.
    "},{"location":"documentation/configuration/overview/#collect_peer_data","title":"collect_peer_data","text":"
    • Default: true
    • Description: If true, collects peer-level data (bytes, last handshake, endpoint, etc.).
    "},{"location":"documentation/configuration/overview/#collect_audit_data","title":"collect_audit_data","text":"
    • Default: true
    • Description: If true, logs certain portal events (such as user logins) to the database.
    "},{"location":"documentation/configuration/overview/#listening_address","title":"listening_address","text":"
    • Default: :8787
    • Description: Address and port for the integrated Prometheus metric server (e.g., :8787 or 127.0.0.1:8787).
    "},{"location":"documentation/configuration/overview/#mail","title":"Mail","text":"

    Options for configuring email notifications or sending peer configurations via email.

    "},{"location":"documentation/configuration/overview/#host","title":"host","text":"
    • Default: 127.0.0.1
    • Description: Hostname or IP of the SMTP server.
    "},{"location":"documentation/configuration/overview/#port","title":"port","text":"
    • Default: 25
    • Description: Port number for the SMTP server.
    "},{"location":"documentation/configuration/overview/#encryption","title":"encryption","text":"
    • Default: none
    • Description: SMTP encryption type. Valid values: none, tls, starttls.
    "},{"location":"documentation/configuration/overview/#cert_validation","title":"cert_validation","text":"
    • Default: true
    • Description: If true, validate the SMTP server certificate (relevant if encryption = tls).
    "},{"location":"documentation/configuration/overview/#username","title":"username","text":"
    • Default: (empty)
    • Description: Optional SMTP username for authentication.
    "},{"location":"documentation/configuration/overview/#password","title":"password","text":"
    • Default: (empty)
    • Description: Optional SMTP password for authentication.
    "},{"location":"documentation/configuration/overview/#auth_type","title":"auth_type","text":"
    • Default: plain
    • Description: SMTP authentication type. Valid values: plain, login, crammd5.
    "},{"location":"documentation/configuration/overview/#from","title":"from","text":"
    • Default: Wireguard Portal <noreply@wireguard.local>
    • Description: The default \"From\" address when sending emails.
    "},{"location":"documentation/configuration/overview/#link_only","title":"link_only","text":"
    • Default: false
    • Description: If true, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
    "},{"location":"documentation/configuration/overview/#auth","title":"Auth","text":"

    WireGuard Portal supports multiple authentication strategies, including OpenID Connect (oidc), OAuth (oauth), Passkeys (webauthn) and LDAP (ldap). Each can have multiple providers configured. Below are the relevant keys.

    Some core authentication options are shared across all providers, while others are specific to each provider type.

    "},{"location":"documentation/configuration/overview/#min_password_length","title":"min_password_length","text":"
    • Default: 16
    • Description: Minimum password length for local authentication. This is not enforced for LDAP authentication. The default admin password strength is also enforced by this setting.
    • Important: The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.
    "},{"location":"documentation/configuration/overview/#hide_login_form","title":"hide_login_form","text":"
    • Default: false
    • Description: If true, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method. If no social login providers are configured, the login form is always shown, regardless of this setting.
    • Important: You can still access the login form by adding the ?all query parameter to the login URL (e.g. https://wg.portal/#/login?all).
    "},{"location":"documentation/configuration/overview/#oidc","title":"OIDC","text":"

    The oidc array contains a list of OpenID Connect providers. Below are the properties for each OIDC provider entry inside auth.oidc:

    "},{"location":"documentation/configuration/overview/#provider_name","title":"provider_name","text":"
    • Default: (empty)
    • Description: A unique name for this provider. Must not conflict with other providers.
    "},{"location":"documentation/configuration/overview/#display_name_1","title":"display_name","text":"
    • Default: (empty)
    • Description: A user-friendly name shown on the login page (e.g., \"Login with Google\").
    "},{"location":"documentation/configuration/overview/#base_url","title":"base_url","text":"
    • Default: (empty)
    • Description: The OIDC provider\u2019s base URL (e.g., https://accounts.google.com).
    "},{"location":"documentation/configuration/overview/#client_id","title":"client_id","text":"
    • Default: (empty)
    • Description: The OAuth client ID from the OIDC provider.
    "},{"location":"documentation/configuration/overview/#client_secret","title":"client_secret","text":"
    • Default: (empty)
    • Description: The OAuth client secret from the OIDC provider.
    "},{"location":"documentation/configuration/overview/#extra_scopes","title":"extra_scopes","text":"
    • Default: (empty)
    • Description: A list of additional OIDC scopes (e.g., profile, email).
    "},{"location":"documentation/configuration/overview/#allowed_domains","title":"allowed_domains","text":"
    • Default: (empty)
    • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
    "},{"location":"documentation/configuration/overview/#field_map","title":"field_map","text":"
    • Default: (empty)
    • Description: Maps OIDC claims to WireGuard Portal user fields.
    • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

      Field Typical OIDC Claim Explanation user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it\u2019s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it\u2019s unique. email email The user\u2019s email address as provided by the IdP. Not always verified, depending on IdP settings. firstname given_name The user\u2019s first name, typically provided by the IdP in the given_name claim. lastname family_name The user\u2019s last (family) name, typically provided by the IdP in the family_name claim. phone phone_number The user\u2019s phone number. This may require additional scopes/permissions from the IdP to access. department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute). is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership. user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.
    "},{"location":"documentation/configuration/overview/#admin_mapping","title":"admin_mapping","text":"
    • Default: (empty)
    • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
      • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string \"true\" (^true$).
      • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.
    "},{"location":"documentation/configuration/overview/#registration_enabled","title":"registration_enabled","text":"
    • Default: (empty)
    • Description: If true, a new user will be created in WireGuard Portal if not already present.
    "},{"location":"documentation/configuration/overview/#log_user_info","title":"log_user_info","text":"
    • Default: (empty)
    • Description: If true, OIDC user data is logged at the trace level upon login (for debugging).
    "},{"location":"documentation/configuration/overview/#oauth","title":"OAuth","text":"

    The oauth array contains a list of plain OAuth2 providers. Below are the properties for each OAuth provider entry inside auth.oauth:

    "},{"location":"documentation/configuration/overview/#provider_name_1","title":"provider_name","text":"
    • Default: (empty)
    • Description: A unique name for this provider. Must not conflict with other providers.
    "},{"location":"documentation/configuration/overview/#display_name_2","title":"display_name","text":"
    • Default: (empty)
    • Description: A user-friendly name shown on the login page.
    "},{"location":"documentation/configuration/overview/#client_id_1","title":"client_id","text":"
    • Default: (empty)
    • Description: The OAuth client ID for the provider.
    "},{"location":"documentation/configuration/overview/#client_secret_1","title":"client_secret","text":"
    • Default: (empty)
    • Description: The OAuth client secret for the provider.
    "},{"location":"documentation/configuration/overview/#auth_url","title":"auth_url","text":"
    • Default: (empty)
    • Description: URL of the authentication endpoint.
    "},{"location":"documentation/configuration/overview/#token_url","title":"token_url","text":"
    • Default: (empty)
    • Description: URL of the token endpoint.
    "},{"location":"documentation/configuration/overview/#user_info_url","title":"user_info_url","text":"
    • Default: (empty)
    • Description: URL of the user information endpoint.
    "},{"location":"documentation/configuration/overview/#scopes","title":"scopes","text":"
    • Default: (empty)
    • Description: A list of OAuth scopes.
    "},{"location":"documentation/configuration/overview/#allowed_domains_1","title":"allowed_domains","text":"
    • Default: (empty)
    • Description: A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
    "},{"location":"documentation/configuration/overview/#field_map_1","title":"field_map","text":"
    • Default: (empty)
    • Description: Maps OAuth attributes to WireGuard Portal fields.
    • Available fields: user_identifier, email, firstname, lastname, phone, department, is_admin, user_groups.

      Field Typical Claim Explanation user_identifier sub or preferred_username A unique identifier for the user. Often the OIDC sub claim is used because it\u2019s guaranteed to be unique for the user within the IdP. Some providers also support preferred_username if it\u2019s unique. email email The user\u2019s email address as provided by the IdP. Not always verified, depending on IdP settings. firstname given_name The user\u2019s first name, typically provided by the IdP in the given_name claim. lastname family_name The user\u2019s last (family) name, typically provided by the IdP in the family_name claim. phone phone_number The user\u2019s phone number. This may require additional scopes/permissions from the IdP to access. department Custom claim (e.g., department) If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., department, org, or another attribute). is_admin Custom claim or derived role If the IdP returns a role or admin flag, you can map that to is_admin. Often this is managed through custom claims or group membership. user_groups groups or another custom claim A list of group memberships for the user. Some IdPs provide groups out of the box; others require custom claims or directory lookups.
    "},{"location":"documentation/configuration/overview/#admin_mapping_1","title":"admin_mapping","text":"
    • Default: (empty)
    • Description: WgPortal can grant a user admin rights by matching the value of the is_admin claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the user_group claim. The regular expressions are defined in admin_value_regex and admin_group_regex.
    • admin_value_regex: A regular expression to match the is_admin claim. By default, this expression matches the string \"true\" (^true$).
    • admin_group_regex: A regular expression to match the user_groups claim. Each entry in the user_groups claim is checked against this regex.
    "},{"location":"documentation/configuration/overview/#registration_enabled_1","title":"registration_enabled","text":"
    • Default: (empty)
    • Description: If true, new users are created automatically on successful login.
    "},{"location":"documentation/configuration/overview/#log_user_info_1","title":"log_user_info","text":"
    • Default: (empty)
    • Description: If true, logs user info at the trace level upon login.
    "},{"location":"documentation/configuration/overview/#ldap","title":"LDAP","text":"

    The ldap array contains a list of LDAP authentication providers. Below are the properties for each LDAP provider entry inside auth.ldap:

    "},{"location":"documentation/configuration/overview/#provider_name_2","title":"provider_name","text":"
    • Default: (empty)
    • Description: A unique name for this provider. Must not conflict with other providers.
    "},{"location":"documentation/configuration/overview/#url","title":"url","text":"
    • Default: (empty)
    • Description: The LDAP server URL (e.g., ldap://srv-ad01.company.local:389).
    "},{"location":"documentation/configuration/overview/#start_tls","title":"start_tls","text":"
    • Default: (empty)
    • Description: If true, use STARTTLS to secure the LDAP connection.
    "},{"location":"documentation/configuration/overview/#cert_validation_1","title":"cert_validation","text":"
    • Default: (empty)
    • Description: If true, validate the LDAP server\u2019s TLS certificate.
    "},{"location":"documentation/configuration/overview/#tls_certificate_path","title":"tls_certificate_path","text":"
    • Default: (empty)
    • Description: Path to a TLS certificate if needed for LDAP connections.
    "},{"location":"documentation/configuration/overview/#tls_key_path","title":"tls_key_path","text":"
    • Default: (empty)
    • Description: Path to the corresponding TLS certificate key.
    "},{"location":"documentation/configuration/overview/#base_dn","title":"base_dn","text":"
    • Default: (empty)
    • Description: The base DN for user searches (e.g., DC=COMPANY,DC=LOCAL).
    "},{"location":"documentation/configuration/overview/#bind_user","title":"bind_user","text":"
    • Default: (empty)
    • Description: The bind user for LDAP (e.g., company\\\\ldap_wireguard or ldap_wireguard@company.local).
    "},{"location":"documentation/configuration/overview/#bind_pass","title":"bind_pass","text":"
    • Default: (empty)
    • Description: The bind password for LDAP authentication.
    "},{"location":"documentation/configuration/overview/#field_map_2","title":"field_map","text":"
    • Default: (empty)
    • Description: Maps LDAP attributes to WireGuard Portal fields.

      • Available fields: user_identifier, email, firstname, lastname, phone, department, memberof.
      WireGuard Portal Field Typical LDAP Attribute Short Description user_identifier sAMAccountName / uid Uniquely identifies the user within the LDAP directory. email mail / userPrincipalName Stores the user's primary email address. firstname givenName Contains the user's first (given) name. lastname sn Contains the user's last (surname) name. phone telephoneNumber / mobile Holds the user's phone or mobile number. department departmentNumber / ou Specifies the department or organizational unit of the user. memberof memberOf Lists the groups and roles to which the user belongs.
    "},{"location":"documentation/configuration/overview/#login_filter","title":"login_filter","text":"
    • Default: (empty)
    • Description: An LDAP filter to restrict which users can log in. Use {{login_identifier}} to insert the username. For example:
      (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))\n
    • Important: The login_filter must always be a valid LDAP filter. It should at most return one user. If the filter returns multiple or no users, the login will fail.
    "},{"location":"documentation/configuration/overview/#admin_group","title":"admin_group","text":"
    • Default: (empty)
    • Description: A specific LDAP group whose members are considered administrators in WireGuard Portal. For example:
      CN=WireGuardAdmins,OU=Some-OU,DC=YOURDOMAIN,DC=LOCAL\n
    "},{"location":"documentation/configuration/overview/#sync_interval","title":"sync_interval","text":"
    • Default: (empty)
    • Description: How frequently (in duration, e.g. 30m) to synchronize users from LDAP. Empty or 0 disables sync. Format uses s, m, h, d for seconds, minutes, hours, days, see time.ParseDuration. Only users that match the sync_filter are synchronized, if disable_missing is true, users not found in LDAP are disabled.
    "},{"location":"documentation/configuration/overview/#sync_filter","title":"sync_filter","text":"
    • Default: (empty)
    • Description: An LDAP filter to select which users get synchronized into WireGuard Portal. For example:
      (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))\n
    "},{"location":"documentation/configuration/overview/#disable_missing","title":"disable_missing","text":"
    • Default: (empty)
    • Description: If true, any user not found in LDAP (during sync) is disabled in WireGuard Portal.
    "},{"location":"documentation/configuration/overview/#auto_re_enable","title":"auto_re_enable","text":"
    • Default: (empty)
    • Description: If true, users that where disabled because they were missing (see disable_missing) will be re-enabled once they are found again.
    "},{"location":"documentation/configuration/overview/#registration_enabled_2","title":"registration_enabled","text":"
    • Default: (empty)
    • Description: If true, new user accounts are created in WireGuard Portal upon first login.
    "},{"location":"documentation/configuration/overview/#log_user_info_2","title":"log_user_info","text":"
    • Default: (empty)
    • Description: If true, logs LDAP user data at the trace level upon login.
    "},{"location":"documentation/configuration/overview/#webauthn-passkeys","title":"WebAuthn (Passkeys)","text":"

    The webauthn section contains configuration options for WebAuthn authentication (passkeys).

    "},{"location":"documentation/configuration/overview/#enabled","title":"enabled","text":"
    • Default: true
    • Description: If true, Passkey authentication is enabled. If false, WebAuthn is disabled. Users are encouraged to use Passkeys for secure authentication instead of passwords. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.
    "},{"location":"documentation/configuration/overview/#web","title":"Web","text":"

    The web section contains configuration options for the web server, including the listening address, session management, and CSRF protection. It is important to specify a valid external_url for the web server, especially if you are using a reverse proxy. Without a valid external_url, the login process may fail due to CSRF protection.

    "},{"location":"documentation/configuration/overview/#listening_address_1","title":"listening_address","text":"
    • Default: :8888
    • Description: The listening address and port for the web server (e.g., :8888 to bind on all interfaces or 127.0.0.1:8888 to bind only on the loopback interface). Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.
    "},{"location":"documentation/configuration/overview/#external_url","title":"external_url","text":"
    • Default: http://localhost:8888
    • Description: The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. Important: If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.
    "},{"location":"documentation/configuration/overview/#site_company_name","title":"site_company_name","text":"
    • Default: WireGuard Portal
    • Description: The company name that is shown at the bottom of the web frontend.
    "},{"location":"documentation/configuration/overview/#site_title","title":"site_title","text":"
    • Default: WireGuard Portal
    • Description: The title that is shown in the web frontend.
    "},{"location":"documentation/configuration/overview/#session_identifier","title":"session_identifier","text":"
    • Default: wgPortalSession
    • Description: The session identifier for the web frontend.
    "},{"location":"documentation/configuration/overview/#session_secret","title":"session_secret","text":"
    • Default: very_secret
    • Description: The session secret for the web frontend.
    "},{"location":"documentation/configuration/overview/#csrf_secret","title":"csrf_secret","text":"
    • Default: extremely_secret
    • Description: The CSRF secret.
    "},{"location":"documentation/configuration/overview/#request_logging","title":"request_logging","text":"
    • Default: false
    • Description: Log all HTTP requests.
    "},{"location":"documentation/configuration/overview/#expose_host_info","title":"expose_host_info","text":"
    • Default: false
    • Description: Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.
    "},{"location":"documentation/configuration/overview/#cert_file","title":"cert_file","text":"
    • Default: (empty)
    • Description: (Optional) Path to the TLS certificate file.
    "},{"location":"documentation/configuration/overview/#key_file","title":"key_file","text":"
    • Default: (empty)
    • Description: (Optional) Path to the TLS certificate key file.
    "},{"location":"documentation/configuration/overview/#webhook","title":"Webhook","text":"

    The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal. Further details can be found in the usage documentation.

    "},{"location":"documentation/configuration/overview/#url_1","title":"url","text":"
    • Default: (empty)
    • Description: The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.
    "},{"location":"documentation/configuration/overview/#authentication","title":"authentication","text":"
    • Default: (empty)
    • Description: The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: Bearer <token>.
    "},{"location":"documentation/configuration/overview/#timeout","title":"timeout","text":"
    • Default: 10s
    • Description: The timeout for the webhook request. If the request takes longer than this, it is aborted.
    "},{"location":"documentation/getting-started/binaries/","title":"Binaries","text":"

    Starting from v2, each release includes compiled binaries for supported platforms. These binary versions can be manually downloaded and installed.

    "},{"location":"documentation/getting-started/binaries/#download","title":"Download","text":"

    Make sure that you download the correct binary for your architecture. The available binaries are:

    • wg-portal_linux_amd64 - Linux x86_64
    • wg-portal_linux_arm64 - Linux ARM 64-bit
    • wg-portal_linux_arm_v7 - Linux ARM 32-bit

    With curl:

    curl -L -o wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64 \n

    With wget:

    wget -O wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64\n

    with gh cli:

    gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'\n
    "},{"location":"documentation/getting-started/binaries/#install","title":"Install","text":"
    sudo mkdir -p /opt/wg-portal\nsudo install wg-portal /opt/wg-portal/\n
    "},{"location":"documentation/getting-started/binaries/#unreleased-versions-master-branch-builds","title":"Unreleased versions (master branch builds)","text":"

    Unreleased versions can be fetched directly from the artifacts section of the GitHub Workflow.

    "},{"location":"documentation/getting-started/docker/","title":"Docker","text":""},{"location":"documentation/getting-started/docker/#image-usage","title":"Image Usage","text":"

    The WireGuard Portal Docker image is available on both Docker Hub and GitHub Container Registry. It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.

    This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the linuxserver/wireguard Docker image.

    The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.

    A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:

    ---\nservices:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    restart: unless-stopped\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    cap_add:\n      - NET_ADMIN\n    # Use host network mode for WireGuard and the UI. Ensure that access to the UI is properly secured.\n    network_mode: \"host\"\n    volumes:\n      # left side is the host path, right side is the container path\n      - /etc/wireguard:/etc/wireguard\n      - ./data:/app/data\n      - ./config:/app/config\n

    By default, the webserver for the UI is listening on port 8888 on all available interfaces.

    Volumes for /app/data and /app/config should be used ensure data persistence across container restarts.

    "},{"location":"documentation/getting-started/docker/#wireguard-interface-handling","title":"WireGuard Interface Handling","text":"

    WireGuard Portal supports managing WireGuard interfaces through three distinct deployment methods, providing flexibility based on your system architecture and operational preferences:

    • Directly on the host system: WireGuard Portal can control WireGuard interfaces natively on the host, without using containers. This setup is ideal for environments where direct access to system networking is preferred. To use this method, you need to set the network mode to host in your docker-compose.yml file.

      services:\n  wg-portal:\n    ...\n    network_mode: \"host\"\n    ...\n

      If host networking is used, the WireGuard Portal UI will be accessible on all the host's IP addresses if the listening address is set to :8888 in the configuration file. To avoid this, you can bind the listening address to a specific IP address, for example, the loopback address (127.0.0.1:8888). It is also possible to deploy firewall rules to restrict access to the WireGuard Portal UI.

    • Within the WireGuard Portal Docker container: WireGuard interfaces can be managed directly from within the WireGuard Portal container itself. This is the recommended approach when running WireGuard Portal via Docker, as it encapsulates all functionality in a single, portable container without requiring a separate WireGuard host or image.

      services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    ...\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      # WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)\n      - \"51820:51820/udp\" \n      # Web UI port\n      - \"8888:8888/tcp\"\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n    volumes:\n      # host path : container path\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n

    • Via a separate Docker container: WireGuard Portal can interface with and control WireGuard running in another Docker container, such as the linuxserver/wireguard image. This method is useful in setups that already use linuxserver/wireguard or where you want to isolate the VPN backend from the portal frontend. For this, you need to set the network mode to service:wireguard in your docker-compose.yml file, wireguard is the service name of your WireGuard container.

      services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    ...\n    cap_add:\n      - NET_ADMIN\n    network_mode: \"service:wireguard\" # So we ensure to stay on the same network as the wireguard container.\n    volumes:\n      # host path : container path\n      - ./wg/etc:/etc/wireguard\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n\n  wireguard:\n    image: lscr.io/linuxserver/wireguard:latest\n    container_name: wireguard\n    restart: unless-stopped\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      - \"51820:51820/udp\" # WireGuard port, needs to match the port in wg-portal interface config\n      - \"8888:8888/tcp\" # Noticed that the port of the web UI is exposed in the wireguard container.\n    volumes:\n      - ./wg/etc:/config/wg_confs # We share the configuration (wgx.conf) between wg-portal and wireguard\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n
      As the linuxserver/wireguard image uses wg-quick to manage the interfaces, you need to have at least the following configuration set for WireGuard Portal:
      core:\n  # The WireGuard container uses wg-quick to manage the WireGuard interfaces - this conflicts with WireGuard Portal during startup.\n  # To avoid this, we need to set the restore_state option to false so that wg-quick can create the interfaces.\n  restore_state: false\n  # Usually, there are no existing interfaces in the WireGuard container, so we can set this to false.\n  import_existing: false\nadvanced:\n  # WireGuard Portal needs to export the WireGuard configuration as wg-quick config files so that the WireGuard container can use them.\n  config_storage_path: /etc/wireguard/\n

    "},{"location":"documentation/getting-started/docker/#image-versioning","title":"Image Versioning","text":"

    All images are hosted on Docker Hub at https://hub.docker.com/r/wgportal/wg-portal or in the GitHub Container Registry.

    Version 2 is the current stable release. Version 1 has moved to legacy status and is no longer recommended.

    There are three types of tags in the repository:

    "},{"location":"documentation/getting-started/docker/#semantic-versioned-tags","title":"Semantic versioned tags","text":"

    For example, 2.0.0-rc.1 or v2.0.0-rc.1.

    These are official releases of WireGuard Portal. For production deployments of WireGuard Portal, we strongly recommend using one of these versioned tags instead of the latest or canary tags.

    There are different types of these tags:

    • Major version tags: v2 or 2. These tags always refer to the latest image for WireGuard Portal version 2.
    • Minor version tags: v2.x or 2.0. These tags always refer to the latest image for WireGuard Portal version 2.x.
    • Specific version tags (patch version): v2.0.0 or 2.0.0. These tags denote a very specific release. They correspond to the GitHub tags that we make, and you can see the release notes for them here: https://github.com/h44z/wg-portal/releases. Once these tags for a specific version show up in the Docker repository, they will never change.
    "},{"location":"documentation/getting-started/docker/#the-latest-tag","title":"The latest tag","text":"

    The lastest tag is the latest stable release of WireGuard Portal. For version 2, this is the same as the v2 tag.

    "},{"location":"documentation/getting-started/docker/#the-master-tag","title":"The master tag","text":"

    This is the most recent build to the main branch! It changes a lot and is very unstable.

    We recommend that you don't use it except for development purposes or to test the latest features.

    "},{"location":"documentation/getting-started/docker/#configuration","title":"Configuration","text":"

    You can configure WireGuard Portal using a YAML configuration file. The filepath of the YAML configuration file defaults to /app/config/config.yaml. It is possible to override the configuration filepath using the environment variable WG_PORTAL_CONFIG.

    By default, WireGuard Portal uses an SQLite database. The database is stored in /app/data/sqlite.db.

    You should mount those directories as a volume:

    • /app/data
    • /app/config

    A detailed description of the configuration options can be found here.

    If you want to access configuration files in wg-quick format, you can mount the /etc/wireguard directory inside the container to a location of your choice. Also enable the config_storage_path option in the configuration file:

    advanced:\n  config_storage_path: /etc/wireguard\n

    "},{"location":"documentation/getting-started/helm/","title":"Helm","text":""},{"location":"documentation/getting-started/helm/#installing-the-chart","title":"Installing the Chart","text":"

    To install the chart with the release name wg-portal:

    helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal\n

    This command deploy wg-portal on the Kubernetes cluster in the default configuration. The Values section lists the parameters that can be configured during installation.

    "},{"location":"documentation/getting-started/helm/#values","title":"Values","text":"Key Type Default Description nameOverride string \"\" Partially override resource names (adds suffix) fullnameOverride string \"\" Fully override resource names extraDeploy list [] Array of extra objects to deploy with the release config.advanced tpl/object {} Advanced configuration options. config.auth tpl/object {} Auth configuration options. config.core tpl/object {} Core configuration options. If external admins in auth are defined and there are no admin_user and admin_password defined here, the default admin account will be disabled. config.database tpl/object {} Database configuration options config.mail tpl/object {} Mail configuration options config.statistics tpl/object {} Statistics configuration options config.web tpl/object {} Web configuration options. listening_address will be set automatically from service.web.port. external_url is required to enable ingress and certificate resources. revisionHistoryLimit string 10 The number of old ReplicaSets to retain to allow rollback. workloadType string \"Deployment\" Workload type - Deployment or StatefulSet strategy object {\"type\":\"RollingUpdate\"} Update strategy for the workload Valid values are: RollingUpdate or Recreate for Deployment, RollingUpdate or OnDelete for StatefulSet image.repository string \"ghcr.io/h44z/wg-portal\" Image repository image.pullPolicy string \"IfNotPresent\" Image pull policy image.tag string \"\" Overrides the image tag whose default is the chart appVersion imagePullSecrets list [] Image pull secrets podAnnotations tpl/object {} Extra annotations to add to the pod podLabels object {} Extra labels to add to the pod podSecurityContext object {} Pod Security Context securityContext.capabilities.add list [\"NET_ADMIN\"] Add capabilities to the container initContainers tpl/list [] Pod init containers sidecarContainers tpl/list [] Pod sidecar containers dnsPolicy string \"ClusterFirst\" Set DNS policy for the pod. Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. restartPolicy string \"Always\" Restart policy for all containers within the pod. Valid values are Always, OnFailure or Never. hostNetwork string false. Use the host's network namespace. resources object {} Resources requests and limits command list [] Overwrite pod command args list [] Additional pod arguments env tpl/list [] Additional environment variables envFrom tpl/list [] Additional environment variables from a secret or configMap livenessProbe object {} Liveness probe configuration readinessProbe object {} Readiness probe configuration startupProbe object {} Startup probe configuration volumes tpl/list [] Additional volumes volumeMounts tpl/list [] Additional volumeMounts nodeSelector object {\"kubernetes.io/os\":\"linux\"} Node Selector configuration tolerations list [] Tolerations configuration affinity object {} Affinity configuration service.mixed.enabled bool false Whether to create a single service for the web and wireguard interfaces service.mixed.type string \"LoadBalancer\" Service type service.web.annotations object {} Annotations for the web service service.web.type string \"ClusterIP\" Web service type service.web.port int 8888 Web service port Used for the web interface listener service.web.appProtocol string \"http\" Web service appProtocol. Will be auto set to https if certificate is enabled. service.wireguard.annotations object {} Annotations for the WireGuard service service.wireguard.type string \"LoadBalancer\" Wireguard service type service.wireguard.ports list [51820] Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. service.metrics.port int 8787 ingress.enabled bool false Specifies whether an ingress resource should be created ingress.className string \"\" Ingress class name ingress.annotations object {} Ingress annotations ingress.tls bool false Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret certificate.enabled bool false Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web. certificate.issuer.name string \"\" Certificate issuer name certificate.issuer.kind string \"\" Certificate issuer kind (ClusterIssuer or Issuer) certificate.issuer.group string \"cert-manager.io\" Certificate issuer group certificate.duration string \"\" Optional. Documentation certificate.renewBefore string \"\" Optional. Documentation certificate.commonName string \"\" Optional. Documentation certificate.emailAddresses list [] Optional. Documentation certificate.ipAddresses list [] Optional. Documentation certificate.keystores object {} Optional. Documentation certificate.privateKey object {} Optional. Documentation certificate.secretTemplate object {} Optional. Documentation certificate.subject object {} Optional. Documentation certificate.uris list [] Optional. Documentation certificate.usages list [] Optional. Documentation persistence.enabled bool false Specifies whether an persistent volume should be created persistence.annotations object {} Persistent Volume Claim annotations persistence.storageClass string \"\" Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used. persistence.accessMode string \"ReadWriteOnce\" Persistent Volume Access Mode persistence.size string \"1Gi\" Persistent Volume size persistence.volumeName string \"\" Persistent Volume Name (optional) serviceAccount.create bool true Specifies whether a service account should be created serviceAccount.annotations object {} Service account annotations serviceAccount.automount bool false Automatically mount a ServiceAccount's API credentials serviceAccount.name string \"\" The name of the service account to use. If not set and create is true, a name is generated using the fullname template monitoring.enabled bool false Enable Prometheus monitoring. monitoring.apiVersion string \"monitoring.coreos.com/v1\" API version of the Prometheus resource. Use azmonitoring.coreos.com/v1 for Azure Managed Prometheus. monitoring.kind string \"PodMonitor\" Kind of the Prometheus resource. Could be PodMonitor or ServiceMonitor. monitoring.labels object {} Resource labels. monitoring.annotations object {} Resource annotations. monitoring.interval string 1m Interval at which metrics should be scraped. If not specified config.statistics.data_collection_interval interval is used. monitoring.metricRelabelings list [] Relabelings to samples before ingestion. monitoring.relabelings list [] Relabelings to samples before scraping. monitoring.scrapeTimeout string \"\" Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used. monitoring.jobLabel string \"\" The label to use to retrieve the job name from. monitoring.podTargetLabels object {} Transfers labels on the Kubernetes Pod onto the target. monitoring.dashboard.enabled bool false Enable Grafana dashboard. monitoring.dashboard.annotations object {} Annotations for the dashboard ConfigMap. monitoring.dashboard.labels object {} Additional labels for the dashboard ConfigMap. monitoring.dashboard.namespace string \"\" Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap."},{"location":"documentation/getting-started/reverse-proxy/","title":"Reverse Proxy (HTTPS)","text":""},{"location":"documentation/getting-started/reverse-proxy/#reverse-proxy-for-https","title":"Reverse Proxy for HTTPS","text":"

    For production deployments, always serve the WireGuard Portal over HTTPS. You have two options to secure your connection:

    "},{"location":"documentation/getting-started/reverse-proxy/#reverse-proxy","title":"Reverse Proxy","text":"

    Let a front\u2010end proxy handle HTTPS for you. This also frees you from managing certificates manually and is therefore the preferred option. You can use Nginx, Traefik, Caddy or any other proxy.

    Below is an example using a Docker Compose stack with Traefik. It exposes the WireGuard Portal on https://wg.domain.com and redirects initial HTTP traffic to HTTPS.

    services:\n  reverse-proxy:\n    image: traefik:v3.3\n    restart: unless-stopped\n    command:\n      #- '--log.level=DEBUG'\n      - '--providers.docker.endpoint=unix:///var/run/docker.sock'\n      - '--providers.docker.exposedbydefault=false'\n      - '--entrypoints.web.address=:80'\n      - '--entrypoints.websecure.address=:443'\n      - '--entrypoints.websecure.http3'\n      - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true'\n      - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web'\n      - '--certificatesresolvers.letsencryptresolver.acme.email=your.email@domain.com'\n      - '--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json'\n      #- '--certificatesresolvers.letsencryptresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory'  # just for testing\n    ports:\n      - 80:80 # for HTTP\n      - 443:443/tcp  # for HTTPS\n      - 443:443/udp  # for HTTP/3\n    volumes:\n      - acme-certs:/letsencrypt\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n    labels:\n      - 'traefik.enable=true'\n      # HTTP Catchall for redirecting HTTP -> HTTPS\n      - 'traefik.http.routers.dashboard-catchall.rule=Host(`wg.domain.com`) && PathPrefix(`/`)'\n      - 'traefik.http.routers.dashboard-catchall.entrypoints=web'\n      - 'traefik.http.routers.dashboard-catchall.middlewares=redirect-to-https'\n      - 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'\n\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    container_name: wg-portal\n    restart: unless-stopped\n    logging:\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    cap_add:\n      - NET_ADMIN\n    ports:\n      # host port : container port\n      # WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)\n      - \"51820:51820/udp\"\n      # Web UI port (only available on localhost, Traefik will handle the HTTPS)\n      - \"127.0.0.1:8888:8888/tcp\"\n    sysctls:\n      - net.ipv4.conf.all.src_valid_mark=1\n    volumes:\n      # host path : container path\n      - ./wg/data:/app/data\n      - ./wg/config:/app/config\n    labels:\n      - 'traefik.enable=true'\n      - 'traefik.http.routers.wgportal.rule=Host(`wg.domain.com`)'\n      - 'traefik.http.routers.wgportal.entrypoints=websecure'\n      - 'traefik.http.routers.wgportal.tls.certresolver=letsencryptresolver'\n      - 'traefik.http.routers.wgportal.service=wgportal'\n      - 'traefik.http.services.wgportal.loadbalancer.server.port=8888'\n\nvolumes:\n  acme-certs:\n

    The WireGuard Portal configuration must be updated accordingly so that the correct external URL is set for the web interface:

    web:\n  external_url: https://wg.domain.com\n
    "},{"location":"documentation/getting-started/reverse-proxy/#built-in-tls","title":"Built-in TLS","text":"

    If you prefer to let WireGuard Portal handle TLS itself, you can use the built-in TLS support. In your config.yaml, under the web section, point to your certificate and key files:

    web:\n  cert_file: /path/to/your/fullchain.pem\n  key_file:  /path/to/your/privkey.pem\n

    The web server will then use these files to serve HTTPS traffic directly instead of HTTP.

    "},{"location":"documentation/getting-started/sources/","title":"Sources","text":"

    To build the application from source files, use the Makefile provided in the repository.

    "},{"location":"documentation/getting-started/sources/#requirements","title":"Requirements","text":"
    • Git
    • Make
    • Go: >=1.24.0
    • Node.js with npm: node>=18, npm>=9
    "},{"location":"documentation/getting-started/sources/#build","title":"Build","text":"
    # Get source code\ngit clone https://github.com/h44z/wg-portal -b ${WG_PORTAL_VERSION:-master} --depth 1\ncd wg-portal\n# Build the frontend\nmake frontend\n# Build the backend\nmake build\n
    "},{"location":"documentation/getting-started/sources/#install","title":"Install","text":"

    Compiled binary will be available in ./dist directory.

    For installation instructions, check the Binaries section.

    "},{"location":"documentation/monitoring/prometheus/","title":"Monitoring","text":"

    By default, WG-Portal exposes Prometheus metrics on port 8787 if interface/peer statistic data collection is enabled.

    "},{"location":"documentation/monitoring/prometheus/#exposed-metrics","title":"Exposed Metrics","text":"Metric Type Description wireguard_interface_received_bytes_total gauge Bytes received through the interface. wireguard_interface_sent_bytes_total gauge Bytes sent through the interface. wireguard_peer_last_handshake_seconds gauge Seconds from the last handshake with the peer. wireguard_peer_received_bytes_total gauge Bytes received from the peer. wireguard_peer_sent_bytes_total gauge Bytes sent to the peer. wireguard_peer_up gauge Peer connection state (boolean: 1/0)."},{"location":"documentation/monitoring/prometheus/#prometheus-config","title":"Prometheus Config","text":"

    Add the following scrape job to your Prometheus config file:

    # prometheus.yaml\nscrape_configs:\n  - job_name: wg-portal\n    scrape_interval: 60s\n    static_configs:\n      - targets:\n          - localhost:8787 # Change localhost to IP Address or hostname with WG-Portal\n
    "},{"location":"documentation/monitoring/prometheus/#grafana-dashboard","title":"Grafana Dashboard","text":"

    You may import dashboard.json into your Grafana instance.

    "},{"location":"documentation/rest-api/api-doc/","title":"REST API","text":""},{"location":"documentation/upgrade/v1/","title":"Upgrade","text":"

    Major upgrades between different versions may require special procedures, which are described in the following sections.

    "},{"location":"documentation/upgrade/v1/#upgrade-from-v1-to-v2","title":"Upgrade from v1 to v2","text":"

    Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!

    To start the upgrade process, start the wg-portal binary with the -migrateFrom parameter. The configuration (config.yaml) for WireGuard Portal must be updated and valid before starting the upgrade.

    To upgrade from a previous SQLite database, start wg-portal like:

    ./wg-portal-amd64 -migrateFrom=old_wg_portal.db\n

    You can also specify the database type using the parameter -migrateFromType. Supported database types: mysql, mssql, postgres or sqlite.

    For example:

    ./wg-portal-amd64 -migrateFromType=mysql -migrateFrom='user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local'\n

    The upgrade will transform the old, existing database and store the values in the new database specified in the config.yaml configuration file. Ensure that the new database does not contain any data!

    If you are using Docker, you can adapt the docker-compose.yml file to start the upgrade process:

    services:\n  wg-portal:\n    image: wgportal/wg-portal:v2\n    # ... other settings\n    restart: no\n    command: [\"-migrateFrom=/app/data/old_wg_portal.db\"]\n
    "},{"location":"documentation/usage/backends/","title":"Backends","text":"

    WireGuard Portal can manage WireGuard interfaces and peers on different backends. Each backend represents a system where interfaces actually live. You can register multiple backends and choose which one to use per interface. A global default backend determines where newly created interfaces go (unless you explicitly choose another in the UI).

    Supported backends: - Local (default): Manages interfaces on the host running WireGuard Portal (Linux WireGuard via wgctrl). Use this when the portal should directly configure wg devices on the same server. - MikroTik RouterOS (beta): Manages interfaces and peers on MikroTik devices via the RouterOS REST API. Use this to control WG interfaces on RouterOS v7+.

    How backend selection works: - The default backend is configured at backend.default (local or the id of a defined MikroTik backend). New interfaces created in the UI will use this backend by default. - Each interface stores its backend. You can select a different backend when creating a new interface.

    "},{"location":"documentation/usage/backends/#configuring-mikrotik-backends-routeros-v7","title":"Configuring MikroTik backends (RouterOS v7+)","text":"

    The MikroTik backend is currently marked beta. While basic functionality is implemented, some advanced features are not yet implemented or contain bugs. Please test carefully before using in production.

    The MikroTik backend uses the REST API under a base URL ending with /rest. You can register one or more MikroTik devices as backends for a single WireGuard Portal instance.

    "},{"location":"documentation/usage/backends/#prerequisites-on-mikrotik","title":"Prerequisites on MikroTik:","text":"
    • RouterOS v7 with WireGuard support.
    • REST API enabled and reachable over HTTP(S). A typical base URL is https://:8729/rest or https:///rest depending on your service setup.
    • A dedicated RouterOS user with the following group permissions:
    • api (for logging in via REST API)
    • rest-api (for logging in via REST API)
    • read (to read interface and peer data)
    • write (to create/update interfaces and peers)
    • test (to perform ping checks)
    • sensitive (to read private keys)
    • TLS certificate on the device is recommended. If you use a self-signed certificate during testing, set api_verify_tls: false in wg-portal (not recommended for production).
    • Example WireGuard Portal configuration (config/config.yaml):

      backend:\n  # default backend decides where new interfaces are created\n  default: mikrotik-prod\n\n  mikrotik:\n    - id: mikrotik-prod              # unique id, not \"local\"\n      display_name: RouterOS RB5009  # optional nice name\n      api_url: https://10.10.10.10/rest\n      api_user: wgportal\n      api_password: a-super-secret-password\n      api_verify_tls: true         # set to false only if using self-signed during testing\n      api_timeout: 30s             # maximum request duration\n      concurrency: 5               # limit parallel REST calls to device\n      debug: false                 # verbose logging for this backend\n
      "},{"location":"documentation/usage/backends/#known-limitations","title":"Known limitations:","text":"
      • The MikroTik backend is still in beta. Some features may not work as expected.
      • Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks)
      "},{"location":"documentation/usage/general/","title":"General","text":"

      This documentation section describes the general usage of WireGuard Portal. If you are looking for specific setup instructions, please refer to the Getting Started and Configuration sections, for example, using a Docker deployment.

      "},{"location":"documentation/usage/general/#basic-concepts","title":"Basic Concepts","text":"

      WireGuard Portal is a web-based configuration portal for WireGuard server management. It allows managing multiple WireGuard interfaces and users from a single web UI. WireGuard Interfaces can be categorized into three types:

      • Server: A WireGuard server interface that to which multiple peers can connect. In this mode, it is possible to specify default settings for all peers, such as the IP address range, DNS servers, and MTU size.
      • Client: A WireGuard client interface that can be used to connect to a WireGuard server. Usually, such an interface has exactly one peer.
      • Unknown: This is the default type for imported interfaces. It is encouraged to change the type to either Server or Client after importing the interface.
      "},{"location":"documentation/usage/general/#accessing-the-web-ui","title":"Accessing the Web UI","text":"

      The web UI should be accessed via the URL specified in the external_url property of the configuration file. By default, WireGuard Portal listens on port 8888 for HTTP connections. Check the Security section for more information on securing the web UI.

      So the default URL to access the web UI is:

      http://localhost:8888\n

      A freshly set-up WireGuard Portal instance will have a default admin user with the username admin@wgportal.local and the password wgportal-default. You can and should override the default credentials in the configuration file. Make sure to change the default password immediately after the first login!

      "},{"location":"documentation/usage/general/#basic-ui-description","title":"Basic UI Description","text":"

      As seen in the screenshot above, the web UI is divided into several sections which are accessible via the navigation bar on the top of the screen.

      1. Home: The landing page of WireGuard Portal. It provides a staring point for the user to access the different sections of the web UI. It also provides quick links to WireGuard Client downloads or official documentation.
      2. Interfaces: This section allows you to manage the WireGuard interfaces. You can add, edit, or delete interfaces, as well as view their status and statistics. Peers for each interface can be managed here as well.
      3. Users: This section allows you to manage the users of WireGuard Portal. You can add, edit, or delete users, as well as view their status and statistics.
      4. Key Generator: This section allows you to generate WireGuard keys locally on your browser. The generated keys are never sent to the server. This is useful if you want to generate keys for a new peer without having to store the private keys in the database.
      5. Profile / Settings: This section allows you to access your own profile page, settings, and audit logs.
      "},{"location":"documentation/usage/general/#interface-view","title":"Interface View","text":"

      The interface view provides an overview of the WireGuard interfaces and peers configured in WireGuard Portal.

      The most important elements are:

      1. Interface Selector: This dropdown allows you to select the WireGuard interface you want to manage. All further actions will be performed on the selected interface.
      2. Create new Interface: This button allows you to create a new WireGuard interface.
      3. Interface Overview: This section provides an overview of the selected WireGuard interface. It shows the interface type, number of peers, and other important information.
      4. List of Peers: This section provides a list of all peers associated with the selected WireGuard interface. You can view, add, edit, or delete peers from this list.
      5. Add new Peer: This button allows you to add a new peer to the selected WireGuard interface.
      6. Add multiple Peers: This button allows you to add multiple peers to the selected WireGuard interface. This is useful if you want to add a large number of peers at once.
      "},{"location":"documentation/usage/ldap/","title":"LDAP","text":"

      WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. You can even register multiple LDAP servers side-by-side. When someone logs in via LDAP, their specific provider is remembered, so there's no risk of cross-provider conflicts. Details on the log-in process can be found in the Security documentation.

      If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. The synchronization process can be fine-tuned by multiple parameters, which are described below.

      "},{"location":"documentation/usage/ldap/#ldap-synchronization","title":"LDAP Synchronization","text":"

      WireGuard Portal can automatically synchronize users from LDAP to the database. To enable this feature, set the sync_interval property in the LDAP provider configuration to a value greater than \"0\". The value is a string representing a duration, such as \"15m\" for 15 minutes or \"1h\" for 1 hour (check the exact format definition for details). The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. Also make sure that the sync_filter property is a well-formed LDAP filter, or synchronization will fail.

      "},{"location":"documentation/usage/ldap/#limiting-synchronization-to-specific-users","title":"Limiting Synchronization to Specific Users","text":"

      Use the sync_filter property in your LDAP provider block to restrict which users get synchronized. It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database.

      For example, to import only users with a mail attribute:

      auth:\n  ldap:\n    - id: ldap\n      # ... other settings\n      sync_filter: (mail=*)\n

      "},{"location":"documentation/usage/ldap/#disable-missing-users","title":"Disable Missing Users","text":"

      If you set the disable_missing property to true, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. All peers associated with that user will also be disabled.

      If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the auto_re_enable property to true. This will only re-enable the user if they where disabled by the synchronization process. Manually disabled users will not be re-enabled.

      "},{"location":"documentation/usage/security/","title":"Security","text":"

      This section describes the security features available to administrators for hardening WireGuard Portal and protecting its data.

      "},{"location":"documentation/usage/security/#authentication","title":"Authentication","text":"

      WireGuard Portal supports multiple authentication methods, including:

      • Local user accounts
      • LDAP authentication
      • OAuth and OIDC authentication
      • Passkey authentication (WebAuthn)

      Users can have two roles which limit their permissions in WireGuard Portal:

      • User: Can manage their own account and peers.
      • Admin: Can manage all users and peers, including the ability to manage WireGuard interfaces.
      "},{"location":"documentation/usage/security/#password-security","title":"Password Security","text":"

      WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. Local users are stored in the database, while LDAP users are authenticated against an external LDAP server.

      On initial startup, WireGuard Portal automatically creates a local admin account with the password wgportal-default.

      This password must be changed immediately after the first login.

      The minimum password length for all local users can be configured in the auth section of the configuration file. The default value is 16 characters, see min_password_length. The minimum password length is also enforced for the default admin user.

      "},{"location":"documentation/usage/security/#passkey-webauthn-authentication","title":"Passkey (WebAuthn) Authentication","text":"

      Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. This feature is enabled by default and can be configured in the webauthn section of the configuration file.

      Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked.

      Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback).

      To register a Passkey, open the settings page (1) in the web UI and click on the \"Register Passkey\" (2) button.

      "},{"location":"documentation/usage/security/#oauth-and-oidc-authentication","title":"OAuth and OIDC Authentication","text":"

      WireGuard Portal supports OAuth and OIDC authentication. You can use any OAuth or OIDC provider that supports the authorization code flow, such as Google, GitHub, or Keycloak.

      For OAuth or OIDC to work, you need to configure the external_url property in the web section of the configuration file. If you are planning to expose the portal to the internet, make sure that the external_url is configured to use HTTPS.

      To add OIDC or OAuth authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth provider and configure a new authentication provider in the auth section of the configuration file. Make sure that each configured provider has a unique provider_name property set. Samples can be seen here.

      "},{"location":"documentation/usage/security/#limiting-login-to-specific-domains","title":"Limiting Login to Specific Domains","text":"

      You can limit the login to specific domains by setting the allowed_domains property for OAuth or OIDC providers. This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. For example, if you want to allow only users with an email address ending in outlook.com to log in, set the property as follows:

      auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      allowed_domains:\n        - \"outlook.com\"\n
      "},{"location":"documentation/usage/security/#limit-login-to-existing-users","title":"Limit Login to Existing Users","text":"

      You can limit the login to existing users only by setting the registration_enabled property to false for OAuth or OIDC providers. If registration is enabled, new users will be created in the database when they log in for the first time.

      "},{"location":"documentation/usage/security/#admin-mapping","title":"Admin Mapping","text":"

      You can map users to admin roles based on their attributes in the OAuth or OIDC provider. To do this, set the admin_mapping property for the provider. Administrative access can either be mapped by a specific attribute or by group membership.

      Attribute specific mapping can be achieved by setting the admin_value_regex and the is_admin property. The admin_value_regex property is a regular expression that is matched against the value of the is_admin attribute. The user is granted admin access if the regex matches the attribute value.

      Example:

      auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      field_map:\n        is_admin: \"wg_admin_prop\"\n      admin_mapping:\n        admin_value_regex: \"^true$\"\n
      The example above will grant admin access to users with the wg_admin_prop attribute set to true.

      Group membership mapping can be achieved by setting the admin_group_regex and user_groups property. The admin_group_regex property is a regular expression that is matched against the group names of the user. The user is granted admin access if the regex matches any of the group names.

      Example:

      auth:\n  oidc:\n    - provider_name: \"oidc1\"\n      # ... other settings\n      field_map:\n        user_groups: \"groups\"\n      admin_mapping:\n        admin_group_regex: \"^the-admin-group$\"\n
      The example above will grant admin access to users who are members of the the-admin-group group.

      "},{"location":"documentation/usage/security/#ldap-authentication","title":"LDAP Authentication","text":"

      WireGuard Portal supports LDAP authentication. You can use any LDAP server that supports the LDAP protocol, such as Active Directory or OpenLDAP. Multiple LDAP servers can be configured in the auth section of the configuration file. WireGuard Portal remembers the authentication provider of the user and therefore avoids conflicts between multiple LDAP providers.

      To configure LDAP authentication, create a new ldap authentication provider in the auth section of the configuration file.

      "},{"location":"documentation/usage/security/#limiting-login-to-specific-users","title":"Limiting Login to Specific Users","text":"

      You can limit the login to specific users by setting the login_filter property for LDAP provider. This filter uses the LDAP search filter syntax. The username can be inserted into the query by placing the {{login_identifier}} placeholder in the filter. This placeholder will then be replaced with the username entered by the user during login.

      For example, if you want to allow only users with the objectClass attribute set to organizationalPerson to log in, set the property as follows:

      auth:\n  ldap:\n    - provider_name: \"ldap1\"\n      # ... other settings\n      login_filter: \"(&(objectClass=organizationalPerson)(uid={{login_identifier}}))\"\n

      The login_filter should always be designed to return at most one user.

      "},{"location":"documentation/usage/security/#limit-login-to-existing-users_1","title":"Limit Login to Existing Users","text":"

      You can limit the login to existing users only by setting the registration_enabled property to false for LDAP providers. If registration is enabled, new users will be created in the database when they log in for the first time.

      "},{"location":"documentation/usage/security/#admin-mapping_1","title":"Admin Mapping","text":"

      You can map users to admin roles based on their group membership in the LDAP server. To do this, set the admin_group and memberof property for the provider. The admin_group property defines the distinguished name of the group that is allowed to log in as admin. All groups that are listed in the memberof attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access.

      "},{"location":"documentation/usage/security/#ui-and-api-access","title":"UI and API Access","text":"

      WireGuard Portal provides a web UI and a REST API for user interaction. It is important to secure these interfaces to prevent unauthorized access and data breaches.

      "},{"location":"documentation/usage/security/#https","title":"HTTPS","text":"

      It is recommended to use HTTPS for all communication with the portal to prevent eavesdropping.

      Event though, WireGuard Portal supports HTTPS out of the box, it is recommended to use a reverse proxy like Nginx or Traefik to handle SSL termination and other security features. A detailed explanation is available in the Reverse Proxy section.

      "},{"location":"documentation/usage/webhooks/","title":"Webhooks","text":"

      Webhooks allow WireGuard Portal to notify external services about events such as user creation, device changes, or configuration updates. This enables integration with other systems and automation workflows.

      When webhooks are configured and a specified event occurs, WireGuard Portal sends an HTTP POST request to the configured webhook URL. The payload contains event-specific data in JSON format.

      "},{"location":"documentation/usage/webhooks/#configuration","title":"Configuration","text":"

      All available configuration options for webhooks can be found in the configuration overview.

      A basic webhook configuration looks like this:

      webhook:\n  url: https://your-service.example.com/webhook\n
      "},{"location":"documentation/usage/webhooks/#security","title":"Security","text":"

      Webhooks can be secured by using a shared secret. This secret is included in the Authorization header of the webhook request, allowing your service to verify the authenticity of the request. You can set the shared secret in the webhook configuration:

      webhook:\n  url: https://your-service.example.com/webhook\n  secret: \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"\n

      You should also make sure that your webhook endpoint is secured with HTTPS to prevent eavesdropping and tampering.

      "},{"location":"documentation/usage/webhooks/#available-events","title":"Available Events","text":"

      WireGuard Portal supports various events that can trigger webhooks. The following events are available:

      • create: Triggered when a new entity is created.
      • update: Triggered when an existing entity is updated.
      • delete: Triggered when an entity is deleted.
      • connect: Triggered when a user connects to the VPN.
      • disconnect: Triggered when a user disconnects from the VPN.

      The following entity models are supported for webhook events:

      • user: WireGuard Portal users support creation, update, or deletion events.
      • peer: Peers support creation, update, or deletion events. Via the peer_metric entity, you can also receive connection status updates.
      • peer_metric: Peer metrics support connection status updates, such as when a peer connects or disconnects.
      • interface: WireGuard interfaces support creation, update, or deletion events.
      "},{"location":"documentation/usage/webhooks/#payload-structure","title":"Payload Structure","text":"

      All webhook events send a JSON payload containing relevant data. The structure of the payload depends on the event type and entity involved. A common shell structure for webhook payloads is as follows:

      {\n  \"event\": \"create\", // The event type, e.g. \"create\", \"update\", \"delete\", \"connect\", \"disconnect\"\n  \"entity\": \"user\",  // The entity type, e.g. \"user\", \"peer\", \"peer_metric\", \"interface\"\n  \"identifier\": \"the-user-identifier\", // Unique identifier of the entity, e.g. user ID or peer ID\n  \"payload\": {\n    // The payload of the event, e.g. a Peer model.\n    // Detailed model descriptions are provided below.\n  }\n}\n
      "},{"location":"documentation/usage/webhooks/#payload-models","title":"Payload Models","text":"

      All payload models are encoded as JSON objects. Fields with empty values might be omitted in the payload.

      "},{"location":"documentation/usage/webhooks/#user-payload-entity-user","title":"User Payload (entity: user)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Time of creation UpdatedAt time.Time Time of last update Identifier string Unique user identifier Email string User email Source string Authentication source ProviderName string Name of auth provider IsAdmin bool Whether user has admin privileges Firstname string User's first name (optional) Lastname string User's last name (optional) Phone string Contact phone number (optional) Department string User's department (optional) Notes string Additional notes (optional) Disabled *time.Time When user was disabled DisabledReason string Reason for deactivation Locked *time.Time When user account was locked LockedReason string Reason for being locked"},{"location":"documentation/usage/webhooks/#peer-payload-entity-peer","title":"Peer Payload (entity: peer)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Creation timestamp UpdatedAt time.Time Last update timestamp Endpoint string Peer endpoint address EndpointPublicKey string Public key of peer endpoint AllowedIPsStr string Allowed IPs ExtraAllowedIPsStr string Extra allowed IPs PresharedKey string Pre-shared key for encryption PersistentKeepalive int Keepalive interval in seconds DisplayName string Display name of the peer Identifier string Unique identifier UserIdentifier string Associated user ID (optional) InterfaceIdentifier string Interface this peer is attached to Disabled *time.Time When the peer was disabled DisabledReason string Reason for being disabled ExpiresAt *time.Time Expiration date Notes string Notes for this peer AutomaticallyCreated bool Whether peer was auto-generated PrivateKey string Peer private key PublicKey string Peer public key InterfaceType string Type of the peer interface Addresses []string IP addresses CheckAliveAddress string Address used for alive checks DnsStr string DNS servers DnsSearchStr string DNS search domains Mtu int MTU (Maximum Transmission Unit) FirewallMark uint32 Firewall mark (optional) RoutingTable string Custom routing table (optional) PreUp string Command before bringing up interface PostUp string Command after bringing up interface PreDown string Command before bringing down interface PostDown string Command after bringing down interface"},{"location":"documentation/usage/webhooks/#interface-payload-entity-interface","title":"Interface Payload (entity: interface)","text":"JSON Field Type Description CreatedBy string Creator identifier UpdatedBy string Last updater identifier CreatedAt time.Time Creation timestamp UpdatedAt time.Time Last update timestamp Identifier string Unique identifier PrivateKey string Private key for the interface PublicKey string Public key for the interface ListenPort int Listening port Addresses []string IP addresses DnsStr string DNS servers DnsSearchStr string DNS search domains Mtu int MTU (Maximum Transmission Unit) FirewallMark uint32 Firewall mark RoutingTable string Custom routing table PreUp string Command before bringing up interface PostUp string Command after bringing up interface PreDown string Command before bringing down interface PostDown string Command after bringing down interface SaveConfig bool Whether to save config to file DisplayName string Human-readable name Type string Type of interface DriverType string Driver used Disabled *time.Time When the interface was disabled DisabledReason string Reason for being disabled PeerDefNetworkStr string Default peer network configuration PeerDefDnsStr string Default peer DNS servers PeerDefDnsSearchStr string Default peer DNS search domains PeerDefEndpoint string Default peer endpoint PeerDefAllowedIPsStr string Default peer allowed IPs PeerDefMtu int Default peer MTU PeerDefPersistentKeepalive int Default keepalive value PeerDefFirewallMark uint32 Default firewall mark for peers PeerDefRoutingTable string Default routing table for peers PeerDefPreUp string Default peer pre-up command PeerDefPostUp string Default peer post-up command PeerDefPreDown string Default peer pre-down command PeerDefPostDown string Default peer post-down command"},{"location":"documentation/usage/webhooks/#peer-metrics-payload-entity-peer_metric","title":"Peer Metrics Payload (entity: peer_metric)","text":"JSON Field Type Description Status PeerStatus Current status of the peer Peer Peer Peer data

      PeerStatus sub-structure:

      JSON Field Type Description UpdatedAt time.Time Time of last status update IsConnected bool Is peer currently connected IsPingable bool Can peer be pinged LastPing *time.Time Time of last successful ping BytesReceived uint64 Bytes received from peer BytesTransmitted uint64 Bytes sent to peer Endpoint string Last known endpoint LastHandshake *time.Time Last successful handshake LastSessionStart *time.Time Time the last session began"},{"location":"documentation/usage/webhooks/#example-payloads","title":"Example Payloads","text":"

      The following payload is an example of a webhook event when a peer connects to the VPN:

      {\n  \"event\": \"connect\",\n  \"entity\": \"peer_metric\",\n  \"identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n  \"payload\": {\n    \"Status\": {\n      \"UpdatedAt\": \"2025-06-27T22:20:08.734900034+02:00\",\n      \"IsConnected\": true,\n      \"IsPingable\": false,\n      \"BytesReceived\": 212,\n      \"BytesTransmitted\": 2884,\n      \"Endpoint\": \"10.55.66.77:58756\",\n      \"LastHandshake\": \"2025-06-27T22:19:46.580842776+02:00\",\n      \"LastSessionStart\": \"2025-06-27T22:19:46.580842776+02:00\"\n    },\n    \"Peer\": {\n      \"CreatedBy\": \"admin@wgportal.local\",\n      \"UpdatedBy\": \"admin@wgportal.local\",\n      \"CreatedAt\": \"2025-06-26T21:43:49.251839574+02:00\",\n      \"UpdatedAt\": \"2025-06-27T22:18:39.67763985+02:00\",\n      \"Endpoint\": \"10.55.66.1:51820\",\n      \"EndpointPublicKey\": \"eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=\",\n      \"AllowedIPsStr\": \"10.11.12.0/24,fdfd:d3ad:c0de:1234::/64\",\n      \"ExtraAllowedIPsStr\": \"\",\n      \"PresharedKey\": \"p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=\",\n      \"PersistentKeepalive\": 16,\n      \"DisplayName\": \"Peer Fb5TaziA\",\n      \"Identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n      \"UserIdentifier\": \"admin@wgportal.local\",\n      \"InterfaceIdentifier\": \"wgTesting\",\n      \"AutomaticallyCreated\": false,\n      \"PrivateKey\": \"QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=\",\n      \"PublicKey\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n      \"InterfaceType\": \"client\",\n      \"Addresses\": [\n        \"10.11.12.10/32\",\n        \"fdfd:d3ad:c0de:1234::a/128\"\n      ],\n      \"CheckAliveAddress\": \"\",\n      \"DnsStr\": \"\",\n      \"DnsSearchStr\": \"\",\n      \"Mtu\": 1420\n    }\n  }\n}\n

      Here is another example of a webhook event when a peer is updated:

      {\n  \"event\": \"update\",\n  \"entity\": \"peer\",\n  \"identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n  \"payload\": {\n    \"CreatedBy\": \"admin@wgportal.local\",\n    \"UpdatedBy\": \"admin@wgportal.local\",\n    \"CreatedAt\": \"2025-06-26T21:43:49.251839574+02:00\",\n    \"UpdatedAt\": \"2025-06-27T22:18:39.67763985+02:00\",\n    \"Endpoint\": \"10.55.66.1:51820\",\n    \"EndpointPublicKey\": \"eiVibpi3C2PUPcx2kwA5s09OgHx7AEaKMd33k0LQ5mM=\",\n    \"AllowedIPsStr\": \"10.11.12.0/24,fdfd:d3ad:c0de:1234::/64\",\n    \"ExtraAllowedIPsStr\": \"\",\n    \"PresharedKey\": \"p9DDeLUSLOdQcjS8ZsBAiqUzwDIUvTyzavRZFuzhvyE=\",\n    \"PersistentKeepalive\": 16,\n    \"DisplayName\": \"Peer Fb5TaziA\",\n    \"Identifier\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n    \"UserIdentifier\": \"admin@wgportal.local\",\n    \"InterfaceIdentifier\": \"wgTesting\",\n    \"AutomaticallyCreated\": false,\n    \"PrivateKey\": \"QBFNBe+7J49ergH0ze2TGUJMFrL/2bOL50Z2cgluYW8=\",\n    \"PublicKey\": \"Fb5TaziAs1WrPBjC/MFbWsIelVXvi0hDKZ3YQM9wmU8=\",\n    \"InterfaceType\": \"client\",\n    \"Addresses\": [\n      \"10.11.12.10/32\",\n      \"fdfd:d3ad:c0de:1234::a/128\"\n    ],\n    \"CheckAliveAddress\": \"\",\n    \"DnsStr\": \"\",\n    \"DnsSearchStr\": \"\",\n    \"Mtu\": 1420\n  }\n}\n
      "}]} \ No newline at end of file diff --git a/master/sitemap.xml b/master/sitemap.xml index 9e23b10..3874f93 100644 --- a/master/sitemap.xml +++ b/master/sitemap.xml @@ -2,70 +2,70 @@ https://wgportal.org/master/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/overview/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/configuration/examples/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/configuration/overview/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/getting-started/binaries/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/getting-started/docker/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/getting-started/helm/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/getting-started/reverse-proxy/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/getting-started/sources/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/monitoring/prometheus/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/rest-api/api-doc/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/upgrade/v1/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/usage/backends/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/usage/general/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/usage/ldap/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/usage/security/ - 2025-10-03 + 2025-10-04 https://wgportal.org/master/documentation/usage/webhooks/ - 2025-10-03 + 2025-10-04 \ No newline at end of file diff --git a/master/sitemap.xml.gz b/master/sitemap.xml.gz index 116eef23aecb798f3b6fd9befeb269e94e7a8cb9..07eaa410ce2d283130f712a6ae2c09637506a7f6 100644 GIT binary patch delta 30 mcmaFD^n{6BzMF%iA^E{Xc3H;N8&%C2IX=nWFWSP%zyJWBLkZ;o delta 30 mcmaFD^n{6BzMF%CLFoQOc3DR2jjHC19Gwy0{%_%AU;qG#+zA-~ diff --git a/master/stylesheets/img-comparison-slider.css b/master/stylesheets/img-comparison-slider.css new file mode 100644 index 0000000..f73d40f --- /dev/null +++ b/master/stylesheets/img-comparison-slider.css @@ -0,0 +1,15 @@ +img-comparison-slider { + visibility: hidden; +} + +img-comparison-slider [slot='second'] { + display: none; +} + +img-comparison-slider.rendered { + visibility: inherit; +} + +img-comparison-slider.rendered [slot='second'] { + display: unset; +} diff --git a/master/theme-overrides/layouts/home.html b/master/theme-overrides/layouts/home.html index 447ab19..3ab842e 100644 --- a/master/theme-overrides/layouts/home.html +++ b/master/theme-overrides/layouts/home.html @@ -300,6 +300,59 @@ background: var(--md-accent-fg-color--transparent); } +.before, +.after { + margin: 0; +} + +.after figcaption { + background: #fff; + font-weight: bold; + border: 1px solid #c0c0c0; + color: #000000; + opacity: 0.9; + padding: 9px; + position: absolute; + top: 100%; + transform: translateY(-100%); + line-height: 100%; +} + +.before figcaption { + background: #000; + font-weight: bold; + border: 1px solid #c0c0c0; + color: #ffffff; + opacity: 0.9; + padding: 9px; + position: absolute; + top: 100%; + transform: translateY(-100%); + line-height: 100%; +} + +.before figcaption { + left: 0px; +} +.after figcaption { + right: 0px; +} +.custom-animated-handle { + transition: transform 0.2s; +} + +.slider-with-animated-handle:hover .custom-animated-handle { + transform: scale(1.2); +} +.md-typeset img-comparison-slider figure { + margin: initial; +} + +.first-overlay { + color: #000; +} + + @@ -326,11 +379,34 @@
      - +
      + +
      + Light Mode +
      Light Mode
      +
      +
      + Dark Mode +
      Dark Mode
      +
      + + + + + + + + +
      +