Working with WireGuard Configurations
Learn how to parse, modify, and manage WireGuard configuration files programmatically using the parse and stringify functions.
Overview
The @kriper0nind/wg-utils library provides powerful tools for working with WireGuard configuration files. You can read, parse, modify, and write configuration files using simple JavaScript objects, making it easy to programmatically manage your VPN setups.
Core Functions
parse(configText: string)
Converts a WireGuard configuration file (text) into a JavaScript object for easy manipulation.
Parameters:configText(string): The raw configuration file content
Returns: A configuration object with Interface and Peers properties
stringify(config: object)
Converts a configuration object back into WireGuard configuration file format.
Parameters:config(object): Configuration object withInterfaceandPeersproperties
Returns: A string in WireGuard configuration format
Configuration Structure
WireGuard configurations have two main sections:
Interface Section
Contains server-side configuration:
Address: Server IP address and subnetListenPort: Port to listen onPrivateKey: Server's private keyPostUp: Commands to run when interface comes upPostDown: Commands to run when interface goes down
Peers Section
Array of peer configurations:
PublicKey: Peer's public keyAllowedIPs: IP addresses this peer can useEndpoint: Optional peer endpointPersistentKeepalive: Keepalive settings
Basic Usage
Reading and Parsing a Configuration
import { parse, stringify } from "@kriper0nind/wg-utils"
import { readFile, writeFile } from "fs/promises"
// Read configuration file
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
// Parse into JavaScript object
const config = parse(configContent)
console.log("Server address:", config.Interface.Address)
console.log("Number of peers:", config.Peers?.length || 0)Writing a Configuration
// Modify the configuration
config.Interface.ListenPort = 51821
// Convert back to WireGuard format
const newConfig = stringify(config)
// Write to file
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")Common Operations
1. Adding a Peer Manually
import { parse, stringify } from "@kriper0nind/wg-utils"
import { readFile, writeFile } from "fs/promises"
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
const config = parse(configContent)
// Ensure Peers array exists
if (!config.Peers) {
config.Peers = []
}
// Add new peer
config.Peers.push({
PublicKey: "client-public-key-here",
AllowedIPs: "10.0.0.2/32"
})
// Save configuration
const newConfig = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")2. Removing a Peer
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
const config = parse(configContent)
// Find and remove peer by public key
const peerIndex = config.Peers.findIndex(peer =>
peer.PublicKey === "client-public-key-to-remove"
)
if (peerIndex !== -1) {
config.Peers.splice(peerIndex, 1)
const newConfig = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")
}3. Modifying Server Settings
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
const config = parse(configContent)
// Update server settings
config.Interface.ListenPort = 51820
config.Interface.Address = "10.0.0.1/24"
// Add custom PostUp command
config.Interface.PostUp = "iptables -A FORWARD -i %i -j ACCEPT"
const newConfig = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")4. Bulk Peer Operations
// Add multiple peers at once
const newPeers = [
{ PublicKey: "peer1-public-key", AllowedIPs: "10.0.0.2/32" },
{ PublicKey: "peer2-public-key", AllowedIPs: "10.0.0.3/32" },
{ PublicKey: "peer3-public-key", AllowedIPs: "10.0.0.4/32" }
]
config.Peers.push(...newPeers)
// Or replace all peers
config.Peers = newPeersAdvanced Examples
Creating a Complete Server Configuration
import { generateKeys } from "@kriper0nind/wg-utils"
// Generate server keys
const serverKeys = await generateKeys()
// Create configuration object
const config = {
Interface: {
Address: "10.0.0.1/24",
ListenPort: 51820,
PrivateKey: serverKeys.privateKey,
PostUp: "iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE",
PostDown: "iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE"
},
Peers: []
}
// Convert to WireGuard format and save
const configText = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", configText, "utf-8")Validating Configuration
function validateConfig(config) {
const errors = []
// Check Interface section
if (!config.Interface) {
errors.push("Missing Interface section")
} else {
if (!config.Interface.PrivateKey) {
errors.push("Missing PrivateKey in Interface")
}
if (!config.Interface.Address) {
errors.push("Missing Address in Interface")
}
}
// Check Peers section
if (config.Peers) {
config.Peers.forEach((peer, index) => {
if (!peer.PublicKey) {
errors.push(`Peer ${index}: Missing PublicKey`)
}
if (!peer.AllowedIPs) {
errors.push(`Peer ${index}: Missing AllowedIPs`)
}
})
}
return errors
}
// Usage
const config = parse(configContent)
const errors = validateConfig(config)
if (errors.length > 0) {
console.error("Configuration errors:", errors)
} else {
console.log("Configuration is valid")
}Configuration Backup and Restore
import { readFile, writeFile, copyFile } from "fs/promises"
// Backup configuration
await copyFile("/etc/wireguard/wg0.conf", "/etc/wireguard/wg0.conf.backup")
// Modify configuration
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
const config = parse(configContent)
// Make changes...
config.Interface.ListenPort = 51821
// Save changes
const newConfig = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")
// If something goes wrong, restore from backup
// await copyFile("/etc/wireguard/wg0.conf.backup", "/etc/wireguard/wg0.conf")Working with Multiple Configurations
async function manageMultipleConfigs() {
const configs = ["wg0", "wg1", "wg2"]
for (const configName of configs) {
const filePath = `/etc/wireguard/${configName}.conf`
try {
const content = await readFile(filePath, "utf-8")
const config = parse(content)
console.log(`${configName}: ${config.Peers?.length || 0} peers`)
// Modify each config as needed
// ...
} catch (error) {
console.error(`Error processing ${configName}:`, error.message)
}
}
}Error Handling
import { parse, stringify } from "@kriper0nind/wg-utils"
try {
const configContent = await readFile("/etc/wireguard/wg0.conf", "utf-8")
const config = parse(configContent)
// Validate parsed configuration
if (!config.Interface) {
throw new Error("Invalid configuration: Missing Interface section")
}
// Make modifications...
const newConfig = stringify(config)
await writeFile("/etc/wireguard/wg0.conf", newConfig, "utf-8")
} catch (error) {
if (error.code === 'ENOENT') {
console.error("Configuration file not found")
} else if (error.message.includes('Invalid configuration')) {
console.error("Configuration file is malformed")
} else {
console.error("Unexpected error:", error.message)
}
}Best Practices
1. Always Backup Before Modifying
// Create backup before making changes
await copyFile("/etc/wireguard/wg0.conf", `/etc/wireguard/wg0.conf.backup.${Date.now()}`)2. Validate Input Data
function isValidPublicKey(key) {
return /^[A-Za-z0-9+/]{43}=$/.test(key)
}
function isValidIP(ip) {
return /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(ip)
}3. Use Proper File Permissions
import { chmod } from "fs/promises"
await writeFile("/etc/wireguard/wg0.conf", configText, "utf-8")
await chmod("/etc/wireguard/wg0.conf", 0o600) // Read/write for owner only4. Handle Missing Sections Gracefully
const config = parse(configContent)
// Ensure Peers array exists
if (!config.Peers) {
config.Peers = []
}
// Safe access to Interface properties
const listenPort = config.Interface?.ListenPort || 51820Configuration File Format
The stringify function generates configuration files in this format:
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = server-private-key
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT
[Peer]
PublicKey = client1-public-key
AllowedIPs = 10.0.0.2/32
[Peer]
PublicKey = client2-public-key
AllowedIPs = 10.0.0.3/32Troubleshooting
Common Issues
- Empty Peers array: Ensure
config.Peers = []before adding peers - Missing Interface section: Check that the original config has an
[Interface]section - Invalid key format: Validate public/private keys before using them
- File permissions: Ensure the process has write access to the config file
Debug Configuration
// Log configuration structure for debugging
console.log("Parsed configuration:", JSON.stringify(config, null, 2))
// Check specific sections
console.log("Interface keys:", Object.keys(config.Interface || {}))
console.log("Number of peers:", config.Peers?.length || 0)