]> git.kreider.io Git - kreider.io.git/commitdiff
new posts
authorRich Kreider <rjkreider@gmail.com>
Mon, 30 Mar 2026 02:30:57 +0000 (22:30 -0400)
committerRich Kreider <rjkreider@gmail.com>
Mon, 30 Mar 2026 02:30:57 +0000 (22:30 -0400)
content/posts/hello.md [deleted file]
content/posts/job_history_email.mdwn [deleted file]
content/posts/mssql-job-history-email.md [new file with mode: 0644]
content/posts/wireguard.md [new file with mode: 0644]
content/posts/wireguard.mdwn [deleted file]

diff --git a/content/posts/hello.md b/content/posts/hello.md
deleted file mode 100644 (file)
index 6a5edc7..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hello, world.
-date: 2026-03-27
----
-Hello world.
diff --git a/content/posts/job_history_email.mdwn b/content/posts/job_history_email.mdwn
deleted file mode 100644 (file)
index 2c593a8..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-# 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]]
diff --git a/content/posts/mssql-job-history-email.md b/content/posts/mssql-job-history-email.md
new file mode 100644 (file)
index 0000000..565e7e9
--- /dev/null
@@ -0,0 +1,386 @@
+---
+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]]
diff --git a/content/posts/wireguard.md b/content/posts/wireguard.md
new file mode 100644 (file)
index 0000000..cce8d94
--- /dev/null
@@ -0,0 +1,382 @@
+---
+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]]
diff --git a/content/posts/wireguard.mdwn b/content/posts/wireguard.mdwn
deleted file mode 100644 (file)
index 57a25fc..0000000
+++ /dev/null
@@ -1,412 +0,0 @@
-# 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]]