+++ /dev/null
----
-title: Hello, world.
-date: 2026-03-27
----
-Hello world.
+++ /dev/null
-# SQL Server: Daily Failed SQL Job Email Report
-
-This guide sets up:
-
-- Database Mail
-- a Database Mail profile
-- a stored procedure that emails failed SQL Agent jobs from the last 24 hours
-- a SQL Agent job scheduled for **11:59 PM daily**
-
----
-
-## What this does
-
-Every night at **11:59 PM**, SQL Server Agent runs a stored procedure in `msdb` that:
-
-- checks SQL Agent job history for failed jobs in the last 24 hours
-- emails the failed job names, failure time, duration, and failure reason
-- sends a "no failures" email if nothing failed
-
----
-
-## Prerequisites
-
-You need:
-
-- SQL Server Agent running
-- an SMTP server you can relay through
-- the following SMTP details:
- - SMTP server name
- - SMTP port
- - whether SSL/TLS is required
- - SMTP username/password if authentication is required
- - From address
- - recipient email address
-
----
-
-## Step 1: Enable Database Mail XPs
-
-Run this first:
-
-```sql
-EXEC sp_configure 'show advanced options', 1;
-RECONFIGURE;
-
-EXEC sp_configure 'Database Mail XPs', 1;
-RECONFIGURE;
-```
-
----
-
-## Step 2: Create the Database Mail account and profile
-
-Replace the sample values below.
-
-### Variables you should replace
-
-- `SQLAlerts` = mail profile name
-- `SQLAlertsAccount` = mail account name
-- `smtp.yourdomain.com` = SMTP server
-- `587` = SMTP port
-- `sqlalerts@yourdomain.com` = From address
-- `SQL Alerts` = display name
-- `smtp-user` = SMTP username
-- `smtp-password` = SMTP password
-
-### Script
-
-```sql
-USE msdb;
-GO
-
--- Create Database Mail account
-EXEC dbo.sysmail_add_account_sp
- @account_name = N'SQLAlertsAccount',
- @description = N'SQL Server Database Mail account for alerts',
- @email_address = N'sqlalerts@yourdomain.com',
- @display_name = N'SQL Alerts',
- @replyto_address = N'sqlalerts@yourdomain.com',
- @mailserver_name = N'smtp.yourdomain.com',
- @port = 587,
- @enable_ssl = 1,
- @username = N'smtp-user',
- @password = N'smtp-password';
-GO
-
--- Create Database Mail profile
-EXEC dbo.sysmail_add_profile_sp
- @profile_name = N'SQLAlerts',
- @description = N'Database Mail profile for SQL alerting';
-GO
-
--- Add account to profile
-EXEC dbo.sysmail_add_profileaccount_sp
- @profile_name = N'SQLAlerts',
- @account_name = N'SQLAlertsAccount',
- @sequence_number = 1;
-GO
-
--- Make profile public/default if desired
-EXEC dbo.sysmail_add_principalprofile_sp
- @profile_name = N'SQLAlerts',
- @principal_name = N'public',
- @is_default = 1;
-GO
-```
-
-### Notes
-
-- If your SMTP server does **not** require authentication, remove `@username` and `@password`.
-- If your SMTP server uses port 25 and no TLS, set:
- - `@port = 25`
- - `@enable_ssl = 0`
-- If your mail relay only allows specific source IPs or hosts, make sure the SQL Server can relay through it.
-
----
-
-## Step 3: Test Database Mail
-
-Run this before doing anything else.
-
-```sql
-EXEC msdb.dbo.sp_send_dbmail
- @profile_name = 'SQLAlerts',
- @recipients = 'you@domain.com',
- @subject = 'SQL Database Mail Test',
- @body = 'Database Mail is working.';
-```
-
-If this fails, stop there and fix SMTP/profile/authentication first.
-
----
-
-## Step 4: Create the stored procedure that emails failed jobs
-
-Replace `you@domain.com` with the real recipient.
-
-```sql
-USE msdb;
-GO
-
-CREATE OR ALTER PROCEDURE dbo.usp_EmailDailyFailedJobs
-AS
-BEGIN
- SET NOCOUNT ON;
-
- DECLARE @Subject nvarchar(255);
- SET @Subject = N'Daily SQL Job Failures - ' + @@SERVERNAME;
-
- IF EXISTS
- (
- SELECT 1
- FROM msdb.dbo.sysjobhistory h
- WHERE h.step_id = 0
- AND h.run_status = 0
- AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(DAY, -1, GETDATE())
- )
- BEGIN
- EXEC msdb.dbo.sp_send_dbmail
- @profile_name = 'SQLAlerts',
- @recipients = 'you@domain.com',
- @subject = @Subject,
- @body = 'Failed SQL Agent jobs in the last 24 hours:',
- @query = N'
-SET NOCOUNT ON;
-
-SELECT
- @@SERVERNAME AS ServerName,
- j.name AS JobName,
- msdb.dbo.agent_datetime(h.run_date, h.run_time) AS FailedAt,
- STUFF(STUFF(RIGHT(''000000'' + CAST(h.run_duration AS varchar(6)), 6), 3, 0, '':''),
- 6, 0, '':'') AS RunDuration_HHMMSS,
- h.message AS FailureReason
-FROM msdb.dbo.sysjobhistory h
-JOIN msdb.dbo.sysjobs j
- ON j.job_id = h.job_id
-WHERE
- h.step_id = 0
- AND h.run_status = 0
- AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(DAY, -1, GETDATE())
-ORDER BY FailedAt DESC;',
- @execute_query_database = 'msdb',
- @query_result_header = 1,
- @query_result_separator = ' | ',
- @query_result_no_padding = 1;
- END
- ELSE
- BEGIN
- EXEC msdb.dbo.sp_send_dbmail
- @profile_name = 'SQLAlerts',
- @recipients = 'you@domain.com',
- @subject = @Subject,
- @body = 'No SQL Agent job failures were recorded in the last 24 hours.';
- END
-END
-GO
-```
-
----
-
-## Step 5: Test the stored procedure manually
-
-```sql
-EXEC msdb.dbo.usp_EmailDailyFailedJobs;
-```
-
-If this does not send mail, do not create the scheduled job yet.
-
----
-
-## Step 6: Create the SQL Agent job scheduled for 11:59 PM daily
-
-```sql
-USE msdb;
-GO
-
-DECLARE @job_id UNIQUEIDENTIFIER;
-
--- Create the job
-EXEC msdb.dbo.sp_add_job
- @job_name = N'Daily SQL Job Failure Email',
- @enabled = 1,
- @description = N'Sends a daily email at 11:59 PM listing failed SQL Agent jobs from the last 24 hours.',
- @owner_login_name = N'sa',
- @job_id = @job_id OUTPUT;
-
--- Add the job step
-EXEC msdb.dbo.sp_add_jobstep
- @job_id = @job_id,
- @step_name = N'Run Failure Report',
- @subsystem = N'TSQL',
- @database_name = N'msdb',
- @command = N'EXEC msdb.dbo.usp_EmailDailyFailedJobs;',
- @on_success_action = 1,
- @on_fail_action = 2;
-
--- Create the schedule
-EXEC msdb.dbo.sp_add_schedule
- @schedule_name = N'Daily 11:59 PM',
- @enabled = 1,
- @freq_type = 4,
- @freq_interval = 1,
- @active_start_time = 235900;
-
--- Attach schedule to job
-EXEC msdb.dbo.sp_attach_schedule
- @job_id = @job_id,
- @schedule_name = N'Daily 11:59 PM';
-
--- Attach job to local server
-EXEC msdb.dbo.sp_add_jobserver
- @job_id = @job_id,
- @server_name = N'(LOCAL)';
-GO
-```
-
----
-
-## Step 7: Test the SQL Agent job immediately
-
-Do not wait until midnight to find out it is broken.
-
-```sql
-EXEC msdb.dbo.sp_start_job
- @job_name = 'Daily SQL Job Failure Email';
-```
-
----
-
-## Step 8: Check job history
-
-```sql
-EXEC msdb.dbo.sp_help_jobhistory
- @job_name = 'Daily SQL Job Failure Email';
-```
-
----
-
-## Helpful verification queries
-
-### Show mail profiles
-
-```sql
-SELECT name, description
-FROM msdb.dbo.sysmail_profile;
-```
-
-### Show which account is tied to which profile
-
-```sql
-SELECT
- p.name AS ProfileName,
- a.name AS AccountName,
- a.email_address,
- a.display_name,
- s.servername,
- s.port
-FROM msdb.dbo.sysmail_profile p
-JOIN msdb.dbo.sysmail_profileaccount pa
- ON p.profile_id = pa.profile_id
-JOIN msdb.dbo.sysmail_account a
- ON pa.account_id = a.account_id
-JOIN msdb.dbo.sysmail_server s
- ON a.account_id = s.account_id;
-```
-
-### Show public/default profiles
-
-```sql
-SELECT
- p.name,
- pp.is_default,
- pp.principal_id
-FROM msdb.dbo.sysmail_principalprofile pp
-JOIN msdb.dbo.sysmail_profile p
- ON pp.profile_id = p.profile_id;
-```
-
-### Show mail log/errors
-
-```sql
-SELECT *
-FROM msdb.dbo.sysmail_event_log
-ORDER BY log_date DESC;
-```
-
-### Show unsent/sent/failed messages
-
-```sql
-SELECT *
-FROM msdb.dbo.sysmail_allitems
-ORDER BY send_request_date DESC;
-```
-
----
-
-## Common failure points
-
-### 1. `sp_send_dbmail` fails
-Usually one of these:
-
-- wrong profile name
-- bad SMTP server/port
-- auth failure
-- relay denied
-- TLS/SSL mismatch
-- firewall blocking outbound SMTP
-
-### 2. Job never runs
-Usually one of these:
-
-- SQL Server Agent service is stopped
-- schedule was not attached
-- job is disabled
-
-### 3. Stored procedure compiles but sends nothing
-Usually one of these:
-
-- there were no failed jobs in the last 24 hours
-- recipient email is wrong
-- mail profile is wrong
-
----
-
-## Blunt recommendation
-
-Build and test in this order:
-
-1. enable Database Mail XPs
-2. create account/profile
-3. test `sp_send_dbmail`
-4. create stored procedure
-5. run stored procedure manually
-6. create Agent job/schedule
-7. manually start Agent job
-8. trust the 11:59 PM schedule only after the manual test works
-
-If step 3 fails, everything after it is pointless until SMTP is fixed.
-
-
-[[!tag notes mssql]]
--- /dev/null
+---
+title: "SQL Server: Daily Failed SQL Job Email Report"
+date: 2026-03-29
+toc: true
+tags: ["mssql"]
+---
+
+This guide sets up:
+
+- Database Mail
+- a Database Mail profile
+- a stored procedure that emails failed SQL Agent jobs from the last 24 hours
+- a SQL Agent job scheduled for **11:59 PM daily**
+
+---
+
+## What this does
+
+Every night at **11:59 PM**, SQL Server Agent runs a stored procedure in `msdb` that:
+
+- checks SQL Agent job history for failed jobs in the last 24 hours
+- emails the failed job names, failure time, duration, and failure reason
+- sends a "no failures" email if nothing failed
+
+---
+
+## Prerequisites
+
+You need:
+
+- SQL Server Agent running
+- an SMTP server you can relay through
+- the following SMTP details:
+ - SMTP server name
+ - SMTP port
+ - whether SSL/TLS is required
+ - SMTP username/password if authentication is required
+ - From address
+ - recipient email address
+
+---
+
+## Step 1: Enable Database Mail XPs
+
+Run this first:
+
+```sql
+EXEC sp_configure 'show advanced options', 1;
+RECONFIGURE;
+
+EXEC sp_configure 'Database Mail XPs', 1;
+RECONFIGURE;
+```
+
+---
+
+## Step 2: Create the Database Mail account and profile
+
+Replace the sample values below.
+
+### Variables you should replace
+
+- `SQLAlerts` = mail profile name
+- `SQLAlertsAccount` = mail account name
+- `smtp.yourdomain.com` = SMTP server
+- `587` = SMTP port
+- `sqlalerts@yourdomain.com` = From address
+- `SQL Alerts` = display name
+- `smtp-user` = SMTP username
+- `smtp-password` = SMTP password
+
+### Script
+
+```sql
+USE msdb;
+GO
+
+-- Create Database Mail account
+EXEC dbo.sysmail_add_account_sp
+ @account_name = N'SQLAlertsAccount',
+ @description = N'SQL Server Database Mail account for alerts',
+ @email_address = N'sqlalerts@yourdomain.com',
+ @display_name = N'SQL Alerts',
+ @replyto_address = N'sqlalerts@yourdomain.com',
+ @mailserver_name = N'smtp.yourdomain.com',
+ @port = 587,
+ @enable_ssl = 1,
+ @username = N'smtp-user',
+ @password = N'smtp-password';
+GO
+
+-- Create Database Mail profile
+EXEC dbo.sysmail_add_profile_sp
+ @profile_name = N'SQLAlerts',
+ @description = N'Database Mail profile for SQL alerting';
+GO
+
+-- Add account to profile
+EXEC dbo.sysmail_add_profileaccount_sp
+ @profile_name = N'SQLAlerts',
+ @account_name = N'SQLAlertsAccount',
+ @sequence_number = 1;
+GO
+
+-- Make profile public/default if desired
+EXEC dbo.sysmail_add_principalprofile_sp
+ @profile_name = N'SQLAlerts',
+ @principal_name = N'public',
+ @is_default = 1;
+GO
+```
+
+### Notes
+
+- If your SMTP server does **not** require authentication, remove `@username` and `@password`.
+- If your SMTP server uses port 25 and no TLS, set:
+ - `@port = 25`
+ - `@enable_ssl = 0`
+- If your mail relay only allows specific source IPs or hosts, make sure the SQL Server can relay through it.
+
+---
+
+## Step 3: Test Database Mail
+
+Run this before doing anything else.
+
+```sql
+EXEC msdb.dbo.sp_send_dbmail
+ @profile_name = 'SQLAlerts',
+ @recipients = 'you@domain.com',
+ @subject = 'SQL Database Mail Test',
+ @body = 'Database Mail is working.';
+```
+
+If this fails, stop there and fix SMTP/profile/authentication first.
+
+---
+
+## Step 4: Create the stored procedure that emails failed jobs
+
+Replace `you@domain.com` with the real recipient.
+
+```sql
+USE msdb;
+GO
+
+CREATE OR ALTER PROCEDURE dbo.usp_EmailDailyFailedJobs
+AS
+BEGIN
+ SET NOCOUNT ON;
+
+ DECLARE @Subject nvarchar(255);
+ SET @Subject = N'Daily SQL Job Failures - ' + @@SERVERNAME;
+
+ IF EXISTS
+ (
+ SELECT 1
+ FROM msdb.dbo.sysjobhistory h
+ WHERE h.step_id = 0
+ AND h.run_status = 0
+ AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(DAY, -1, GETDATE())
+ )
+ BEGIN
+ EXEC msdb.dbo.sp_send_dbmail
+ @profile_name = 'SQLAlerts',
+ @recipients = 'you@domain.com',
+ @subject = @Subject,
+ @body = 'Failed SQL Agent jobs in the last 24 hours:',
+ @query = N'
+SET NOCOUNT ON;
+
+SELECT
+ @@SERVERNAME AS ServerName,
+ j.name AS JobName,
+ msdb.dbo.agent_datetime(h.run_date, h.run_time) AS FailedAt,
+ STUFF(STUFF(RIGHT(''000000'' + CAST(h.run_duration AS varchar(6)), 6), 3, 0, '':''),
+ 6, 0, '':'') AS RunDuration_HHMMSS,
+ h.message AS FailureReason
+FROM msdb.dbo.sysjobhistory h
+JOIN msdb.dbo.sysjobs j
+ ON j.job_id = h.job_id
+WHERE
+ h.step_id = 0
+ AND h.run_status = 0
+ AND msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(DAY, -1, GETDATE())
+ORDER BY FailedAt DESC;',
+ @execute_query_database = 'msdb',
+ @query_result_header = 1,
+ @query_result_separator = ' | ',
+ @query_result_no_padding = 1;
+ END
+ ELSE
+ BEGIN
+ EXEC msdb.dbo.sp_send_dbmail
+ @profile_name = 'SQLAlerts',
+ @recipients = 'you@domain.com',
+ @subject = @Subject,
+ @body = 'No SQL Agent job failures were recorded in the last 24 hours.';
+ END
+END
+GO
+```
+
+---
+
+## Step 5: Test the stored procedure manually
+
+```sql
+EXEC msdb.dbo.usp_EmailDailyFailedJobs;
+```
+
+If this does not send mail, do not create the scheduled job yet.
+
+---
+
+## Step 6: Create the SQL Agent job scheduled for 11:59 PM daily
+
+```sql
+USE msdb;
+GO
+
+DECLARE @job_id UNIQUEIDENTIFIER;
+
+-- Create the job
+EXEC msdb.dbo.sp_add_job
+ @job_name = N'Daily SQL Job Failure Email',
+ @enabled = 1,
+ @description = N'Sends a daily email at 11:59 PM listing failed SQL Agent jobs from the last 24 hours.',
+ @owner_login_name = N'sa',
+ @job_id = @job_id OUTPUT;
+
+-- Add the job step
+EXEC msdb.dbo.sp_add_jobstep
+ @job_id = @job_id,
+ @step_name = N'Run Failure Report',
+ @subsystem = N'TSQL',
+ @database_name = N'msdb',
+ @command = N'EXEC msdb.dbo.usp_EmailDailyFailedJobs;',
+ @on_success_action = 1,
+ @on_fail_action = 2;
+
+-- Create the schedule
+EXEC msdb.dbo.sp_add_schedule
+ @schedule_name = N'Daily 11:59 PM',
+ @enabled = 1,
+ @freq_type = 4,
+ @freq_interval = 1,
+ @active_start_time = 235900;
+
+-- Attach schedule to job
+EXEC msdb.dbo.sp_attach_schedule
+ @job_id = @job_id,
+ @schedule_name = N'Daily 11:59 PM';
+
+-- Attach job to local server
+EXEC msdb.dbo.sp_add_jobserver
+ @job_id = @job_id,
+ @server_name = N'(LOCAL)';
+GO
+```
+
+---
+
+## Step 7: Test the SQL Agent job immediately
+
+Do not wait until midnight to find out it is broken.
+
+```sql
+EXEC msdb.dbo.sp_start_job
+ @job_name = 'Daily SQL Job Failure Email';
+```
+
+---
+
+## Step 8: Check job history
+
+```sql
+EXEC msdb.dbo.sp_help_jobhistory
+ @job_name = 'Daily SQL Job Failure Email';
+```
+
+---
+
+## Helpful verification queries
+
+### Show mail profiles
+
+```sql
+SELECT name, description
+FROM msdb.dbo.sysmail_profile;
+```
+
+### Show which account is tied to which profile
+
+```sql
+SELECT
+ p.name AS ProfileName,
+ a.name AS AccountName,
+ a.email_address,
+ a.display_name,
+ s.servername,
+ s.port
+FROM msdb.dbo.sysmail_profile p
+JOIN msdb.dbo.sysmail_profileaccount pa
+ ON p.profile_id = pa.profile_id
+JOIN msdb.dbo.sysmail_account a
+ ON pa.account_id = a.account_id
+JOIN msdb.dbo.sysmail_server s
+ ON a.account_id = s.account_id;
+```
+
+### Show public/default profiles
+
+```sql
+SELECT
+ p.name,
+ pp.is_default,
+ pp.principal_id
+FROM msdb.dbo.sysmail_principalprofile pp
+JOIN msdb.dbo.sysmail_profile p
+ ON pp.profile_id = p.profile_id;
+```
+
+### Show mail log/errors
+
+```sql
+SELECT *
+FROM msdb.dbo.sysmail_event_log
+ORDER BY log_date DESC;
+```
+
+### Show unsent/sent/failed messages
+
+```sql
+SELECT *
+FROM msdb.dbo.sysmail_allitems
+ORDER BY send_request_date DESC;
+```
+
+---
+
+## Common failure points
+
+### 1. `sp_send_dbmail` fails
+Usually one of these:
+
+- wrong profile name
+- bad SMTP server/port
+- auth failure
+- relay denied
+- TLS/SSL mismatch
+- firewall blocking outbound SMTP
+
+### 2. Job never runs
+Usually one of these:
+
+- SQL Server Agent service is stopped
+- schedule was not attached
+- job is disabled
+
+### 3. Stored procedure compiles but sends nothing
+Usually one of these:
+
+- there were no failed jobs in the last 24 hours
+- recipient email is wrong
+- mail profile is wrong
+
+---
+
+## Blunt recommendation
+
+Build and test in this order:
+
+1. enable Database Mail XPs
+2. create account/profile
+3. test `sp_send_dbmail`
+4. create stored procedure
+5. run stored procedure manually
+6. create Agent job/schedule
+7. manually start Agent job
+8. trust the 11:59 PM schedule only after the manual test works
+
+If step 3 fails, everything after it is pointless until SMTP is fixed.
+
+
+[[!tag notes mssql]]
--- /dev/null
+---
+title: "WireGuard VPN Setup on Debian 13"
+date: 2026-03-29
+toc: true
+tags: ["wireguard","debian","linux"]
+---
+Clean, minimal, self-hosted WireGuard setup with:
+
+- Full tunnel support
+- NAT for internet access
+- Multi-user capability
+- Per-user access control via firewall
+
+## 0. Assumptions
+
+- Public interface: `eth0`
+- VPN subnet: `10.10.10.0/24`
+- Server is publicly reachable
+
+Replace `eth0` with your real interface name if needed, such as `ens18`, `ens3`, or `enp1s0`.
+
+## 1. Install WireGuard
+
+```
+apt update
+apt install wireguard
+```
+
+## 2. Enable IP Forwarding
+
+```
+sysctl -w net.ipv4.ip_forward=1
+```
+
+Persist it:
+
+```
+echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
+sysctl -p
+```
+
+## 3. Generate Keys
+
+```
+cd /etc/wireguard
+
+wg genkey | tee server.key | wg pubkey > server.pub
+wg genkey | tee client.key | wg pubkey > client.pub
+
+chmod 600 *.key
+```
+
+## 4. Server Configuration
+
+Create:
+
+```
+nano /etc/wireguard/wg0.conf
+```
+
+```
+[Interface]
+Address = 10.10.10.1/24
+PrivateKey = <contents of server.key>
+ListenPort = 51820
+
+# NAT + forwarding
+PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
+PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
+
+PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
+PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
+PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
+
+[Peer]
+PublicKey = <contents of client.pub>
+AllowedIPs = 10.10.10.2/32
+```
+
+Notes:
+
+- `Address = 10.10.10.1/24` is the server's VPN IP.
+- `AllowedIPs = 10.10.10.2/32` means that client owns only `10.10.10.2`.
+- Do not reuse keys between devices.
+
+## 5. Start WireGuard
+
+```
+wg-quick up wg0
+systemctl enable wg-quick@wg0
+```
+
+Check status:
+
+```
+wg show
+ip addr show wg0
+```
+
+## 6. Client Configuration
+
+Use this on Windows, Linux, or macOS:
+
+```
+[Interface]
+Address = 10.10.10.2/24
+PrivateKey = <contents of client.key>
+DNS = 1.1.1.1
+
+[Peer]
+PublicKey = <contents of server.pub>
+Endpoint = YOUR_SERVER_IP:51820
+AllowedIPs = 0.0.0.0/0
+PersistentKeepalive = 25
+```
+
+Notes:
+
+- `AllowedIPs = 0.0.0.0/0` sends all client traffic through the VPN.
+
+- If you only want access to specific private networks, replace `0.0.0.0/0` with those subnets.
+
+Example split tunnel:
+
+```
+AllowedIPs = 10.10.10.0/24, 192.168.50.0/24
+```
+
+## 7. Open Firewall Port
+
+If using UFW:
+
+```
+ufw allow 51820/udp
+```
+
+Or raw iptables:
+
+```
+iptables -A INPUT -p udp --dport 51820 -j ACCEPT
+```
+
+## 8. Test Connection
+
+From the client:
+
+```
+curl ip.me
+```
+
+Expected result:
+
+- If full tunnel is enabled, this should show the server's public IP.
+
+Also test:
+
+```
+ping 10.10.10.1
+```
+
+## 9. Add Additional Users
+
+Generate new keys:
+
+```
+wg genkey | tee user2.key | wg pubkey > user2.pub
+chmod 600 user2.key
+```
+
+Add to server config:
+
+```
+[Peer]
+PublicKey = <contents of user2.pub>
+AllowedIPs = 10.10.10.3/32
+```
+
+Apply changes:
+
+```
+wg syncconf wg0 <(wg-quick strip wg0)
+```
+
+Client config example for user2:
+
+```
+[Interface]
+Address = 10.10.10.3/24
+PrivateKey = <contents of user2.key>
+DNS = 1.1.1.1
+
+[Peer]
+PublicKey = <contents of server.pub>
+Endpoint = YOUR_SERVER_IP:51820
+AllowedIPs = 0.0.0.0/0
+PersistentKeepalive = 25
+```
+
+## 10. Access Control and Network Design
+
+WireGuard handles authentication by key.
+
+WireGuard does **not** decide what a connected peer is allowed to reach.
+That is handled by your firewall, typically with `iptables` or `nftables`.
+
+Think of it like this:
+
+- WireGuard key + peer IP = identity
+- Firewall rules = authorization
+
+### Important
+
+The examples below use:
+
+- `10.10.10.2` = full access user
+- `10.10.10.3` = restricted user
+- `10.10.10.4` = internet-only user
+
+Adjust these to your actual peer IP assignments.
+
+### Scenario A: Public server only, no private networks behind it
+
+This is the simplest case.
+
+The server only has:
+
+- `eth0` facing the internet
+- `wg0` for VPN clients
+
+There is no LAN behind the server. You just want clients to:
+
+- connect to the server
+- route internet traffic through it
+- maybe reach services running on the server itself
+
+#### Full tunnel user
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.2 -o eth0 -j ACCEPT
+iptables -A FORWARD -o wg0 -d 10.10.10.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+```
+
+#### Internet-only restricted user
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.4 -o eth0 -j ACCEPT
+iptables -A FORWARD -o wg0 -d 10.10.10.4 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+iptables -A FORWARD -i wg0 -s 10.10.10.4 -j DROP
+```
+
+That last rule says:
+
+- allow that peer out to the internet
+- drop any other forwarded access attempt
+
+If the user also needs to reach services hosted directly on the VPN server itself, that is controlled by `INPUT` rules, not `FORWARD`.
+
+Example: allow SSH from one VPN peer to the server:
+
+```
+iptables -A INPUT -i wg0 -s 10.10.10.3 -p tcp --dport 22 -j ACCEPT
+```
+
+Example: block all other VPN clients from SSH to the server:
+
+```
+iptables -A INPUT -i wg0 -p tcp --dport 22 -j DROP
+```
+
+### Scenario B: Server has private networks behind it
+
+Use this if the Debian server can route to private networks such as:
+
+- `192.168.1.0/24`
+- `10.0.0.0/24`
+- `172.16.50.0/24`
+
+In this case, your VPN clients can be selectively allowed to those networks.
+
+#### Full access user to all routed private networks
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.2 -j ACCEPT
+```
+
+That is broad. It allows that peer to anything the server can route to.
+
+#### Restricted user to a single host
+
+Example: only allow `10.10.10.3` to reach `192.168.1.50`
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.50 -j ACCEPT
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
+```
+
+#### Restricted user to a whole subnet
+
+Example: only allow `10.10.10.3` to reach `192.168.1.0/24`
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
+```
+
+#### Internet plus one private subnet
+
+Example: allow `10.10.10.3` to:
+
+- use the server as an internet gateway
+- access `192.168.1.0/24`
+- nothing else
+
+```
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -o eth0 -j ACCEPT
+iptables -A FORWARD -o wg0 -d 10.10.10.3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
+```
+
+#### Client-side route restriction
+
+You should also tighten the client config.
+
+If a peer should only reach one private subnet, do not send all traffic through the tunnel.
+
+Example client config:
+
+```
+AllowedIPs = 10.10.10.0/24, 192.168.1.0/24
+```
+
+That gives split-tunnel access only to:
+
+- the VPN subnet
+- the private subnet behind the server
+
+## 11. Remove or Revoke Access
+
+Remove a peer immediately:
+
+```
+wg set wg0 peer <public_key> remove
+```
+
+Or edit `wg0.conf` and reload:
+
+```
+wg syncconf wg0 <(wg-quick strip wg0)
+```
+
+## 12. Common Issues
+
+- IP forwarding not enabled
+- Wrong public interface name
+- Firewall blocking UDP 51820
+- NAT rule missing
+- Server not publicly reachable
+- Client `AllowedIPs` too broad or too narrow
+- Client clock wildly wrong
+- Forgot `PersistentKeepalive = 25` for clients behind NAT
+
+## 13. Key Concepts
+
+- Each peer should have its own keypair
+- No passwords or usernames in WireGuard
+- One device should not share another device's key
+- VPN peer IPs should be treated like identities
+- Firewall rules decide what each peer can reach
+
+## 14. Recommended Next Step
+
+This walkthrough uses `iptables` because it is straightforward and familiar.
+
+For a cleaner long-term setup on Debian 13, consider moving the policy to `nftables`, especially if you will have:
+
+- multiple users
+- different access classes
+- persistent firewall rules you want managed sanely
+
+[[!tag wireguard debian linux vpn]]
+++ /dev/null
-# WireGuard VPN Setup on Debian 13
-
-Clean, minimal, self-hosted WireGuard setup with:
-
-- Full tunnel support
-- NAT for internet access
-- Multi-user capability
-- Per-user access control via firewall
-
----
-
-## 0. Assumptions
-
-- Public interface: `eth0`
-- VPN subnet: `10.10.10.0/24`
-- Server is publicly reachable
-
-Replace `eth0` with your real interface name if needed, such as `ens18`, `ens3`, or `enp1s0`.
-
----
-
-## 1. Install WireGuard
-
-```
-apt update
-apt install wireguard
-```
-
----
-
-## 2. Enable IP Forwarding
-
-```
-sysctl -w net.ipv4.ip_forward=1
-```
-
-Persist it:
-
-```
-echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
-sysctl -p
-```
-
----
-
-## 3. Generate Keys
-
-```
-cd /etc/wireguard
-
-wg genkey | tee server.key | wg pubkey > server.pub
-wg genkey | tee client.key | wg pubkey > client.pub
-
-chmod 600 *.key
-```
-
----
-
-## 4. Server Configuration
-
-Create:
-
-```
-nano /etc/wireguard/wg0.conf
-```
-
-```
-[Interface]
-Address = 10.10.10.1/24
-PrivateKey = <contents of server.key>
-ListenPort = 51820
-
-# NAT + forwarding
-PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
-PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
-PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
-
-PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
-PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
-PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
-
-[Peer]
-PublicKey = <contents of client.pub>
-AllowedIPs = 10.10.10.2/32
-```
-
-Notes:
-
-- `Address = 10.10.10.1/24` is the server's VPN IP.
-- `AllowedIPs = 10.10.10.2/32` means that client owns only `10.10.10.2`.
-- Do not reuse keys between devices.
-
----
-
-## 5. Start WireGuard
-
-```
-wg-quick up wg0
-systemctl enable wg-quick@wg0
-```
-
-Check status:
-
-```
-wg show
-ip addr show wg0
-```
-
----
-
-## 6. Client Configuration
-
-Use this on Windows, Linux, or macOS:
-
-```
-[Interface]
-Address = 10.10.10.2/24
-PrivateKey = <contents of client.key>
-DNS = 1.1.1.1
-
-[Peer]
-PublicKey = <contents of server.pub>
-Endpoint = YOUR_SERVER_IP:51820
-AllowedIPs = 0.0.0.0/0
-PersistentKeepalive = 25
-```
-
-Notes:
-
-- `AllowedIPs = 0.0.0.0/0` sends all client traffic through the VPN.
-
-- If you only want access to specific private networks, replace `0.0.0.0/0` with those subnets.
-
-Example split tunnel:
-
-```
-AllowedIPs = 10.10.10.0/24, 192.168.50.0/24
-```
-
----
-
-## 7. Open Firewall Port
-
-If using UFW:
-
-```
-ufw allow 51820/udp
-```
-
-Or raw iptables:
-
-```
-iptables -A INPUT -p udp --dport 51820 -j ACCEPT
-```
-
----
-
-## 8. Test Connection
-
-From the client:
-
-```
-curl ip.me
-```
-
-Expected result:
-
-- If full tunnel is enabled, this should show the server's public IP.
-
-Also test:
-
-```
-ping 10.10.10.1
-```
-
----
-
-## 9. Add Additional Users
-
-Generate new keys:
-
-```
-wg genkey | tee user2.key | wg pubkey > user2.pub
-chmod 600 user2.key
-```
-
-Add to server config:
-
-```
-[Peer]
-PublicKey = <contents of user2.pub>
-AllowedIPs = 10.10.10.3/32
-```
-
-Apply changes:
-
-```
-wg syncconf wg0 <(wg-quick strip wg0)
-```
-
-Client config example for user2:
-
-```
-[Interface]
-Address = 10.10.10.3/24
-PrivateKey = <contents of user2.key>
-DNS = 1.1.1.1
-
-[Peer]
-PublicKey = <contents of server.pub>
-Endpoint = YOUR_SERVER_IP:51820
-AllowedIPs = 0.0.0.0/0
-PersistentKeepalive = 25
-```
-
----
-
-## 10. Access Control and Network Design
-
-WireGuard handles authentication by key.
-
-WireGuard does **not** decide what a connected peer is allowed to reach.
-That is handled by your firewall, typically with `iptables` or `nftables`.
-
-Think of it like this:
-
-- WireGuard key + peer IP = identity
-- Firewall rules = authorization
-
-### Important
-
-The examples below use:
-
-- `10.10.10.2` = full access user
-- `10.10.10.3` = restricted user
-- `10.10.10.4` = internet-only user
-
-Adjust these to your actual peer IP assignments.
-
----
-
-### Scenario A: Public server only, no private networks behind it
-
-This is the simplest case.
-
-The server only has:
-
-- `eth0` facing the internet
-- `wg0` for VPN clients
-
-There is no LAN behind the server. You just want clients to:
-
-- connect to the server
-- route internet traffic through it
-- maybe reach services running on the server itself
-
-#### Full tunnel user
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.2 -o eth0 -j ACCEPT
-iptables -A FORWARD -o wg0 -d 10.10.10.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-```
-
-#### Internet-only restricted user
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.4 -o eth0 -j ACCEPT
-iptables -A FORWARD -o wg0 -d 10.10.10.4 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-iptables -A FORWARD -i wg0 -s 10.10.10.4 -j DROP
-```
-
-That last rule says:
-
-- allow that peer out to the internet
-- drop any other forwarded access attempt
-
-If the user also needs to reach services hosted directly on the VPN server itself, that is controlled by `INPUT` rules, not `FORWARD`.
-
-Example: allow SSH from one VPN peer to the server:
-
-```
-iptables -A INPUT -i wg0 -s 10.10.10.3 -p tcp --dport 22 -j ACCEPT
-```
-
-Example: block all other VPN clients from SSH to the server:
-
-```
-iptables -A INPUT -i wg0 -p tcp --dport 22 -j DROP
-```
-
----
-
-### Scenario B: Server has private networks behind it
-
-Use this if the Debian server can route to private networks such as:
-
-- `192.168.1.0/24`
-- `10.0.0.0/24`
-- `172.16.50.0/24`
-
-In this case, your VPN clients can be selectively allowed to those networks.
-
-#### Full access user to all routed private networks
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.2 -j ACCEPT
-```
-
-That is broad. It allows that peer to anything the server can route to.
-
-#### Restricted user to a single host
-
-Example: only allow `10.10.10.3` to reach `192.168.1.50`
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.50 -j ACCEPT
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
-```
-
-#### Restricted user to a whole subnet
-
-Example: only allow `10.10.10.3` to reach `192.168.1.0/24`
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
-```
-
-#### Internet plus one private subnet
-
-Example: allow `10.10.10.3` to:
-
-- use the server as an internet gateway
-- access `192.168.1.0/24`
-- nothing else
-
-```
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -o eth0 -j ACCEPT
-iptables -A FORWARD -o wg0 -d 10.10.10.3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
-```
-
-#### Client-side route restriction
-
-You should also tighten the client config.
-
-If a peer should only reach one private subnet, do not send all traffic through the tunnel.
-
-Example client config:
-
-```
-AllowedIPs = 10.10.10.0/24, 192.168.1.0/24
-```
-
-That gives split-tunnel access only to:
-
-- the VPN subnet
-- the private subnet behind the server
-
----
-
-## 11. Remove or Revoke Access
-
-Remove a peer immediately:
-
-```
-wg set wg0 peer <public_key> remove
-```
-
-Or edit `wg0.conf` and reload:
-
-```
-wg syncconf wg0 <(wg-quick strip wg0)
-```
-
----
-
-## 12. Common Issues
-
-- IP forwarding not enabled
-- Wrong public interface name
-- Firewall blocking UDP 51820
-- NAT rule missing
-- Server not publicly reachable
-- Client `AllowedIPs` too broad or too narrow
-- Client clock wildly wrong
-- Forgot `PersistentKeepalive = 25` for clients behind NAT
-
----
-
-## 13. Key Concepts
-
-- Each peer should have its own keypair
-- No passwords or usernames in WireGuard
-- One device should not share another device's key
-- VPN peer IPs should be treated like identities
-- Firewall rules decide what each peer can reach
-
----
-
-## 14. Recommended Next Step
-
-This walkthrough uses `iptables` because it is straightforward and familiar.
-
-For a cleaner long-term setup on Debian 13, consider moving the policy to `nftables`, especially if you will have:
-
-- multiple users
-- different access classes
-- persistent firewall rules you want managed sanely
-
-[[!tag wireguard debian linux vpn]]