Overview
Configure DKIM signing for Virtru Gateway to maintain email authenticity after encrypt/decrypt operations.
Why it's required: The gateway modifies message bodies (encryption/decryption), which breaks original DKIM signatures. Re-signing prevents deliverability issues.
Prerequisites
- ✅ Virtru Gateway deployed via Helm
- ✅ Kubernetes cluster access (
kubectlconfigured) - ✅ DNS management access
- ✅ OpenSSL installed locally
Note
Throughout this guide, we use virtru as the namespace and gateway as the Helm release name for example purposes. Replace these with your actual namespace and release name in all commands.
Single Domain Limitation
This configuration supports DKIM signing for one domain (primaryMailingDomain in values.yaml).
Multiple Gateway Modes
DKIM signing is applied to all enabled gateway modes (outbound-encrypt, outbound-decrypt, inbound-encrypt, inbound-decrypt, etc.). The verification commands in this guide use outbound-encrypt as an example. Adjust the StatefulSet and service names accordingly for other modes. See the Gateway Mode Reference table for a complete list.
Step 1: Generate DKIM Keys
Generate a 2048-bit RSA key pair for DKIM signing using OpenSSL.
Prerequisites
OpenSSL Installation:
-
Linux: Usually pre-installed. Verify with
openssl version -
Mac: Pre-installed. Verify with
openssl version -
Windows:
- Use Git Bash (included with Git for Windows)
- Or use WSL (Windows Subsystem for Linux)
- Or install OpenSSL
Generate Keys
Run these commands in your terminal/command prompt:
# Generate private key (2048-bit) openssl genrsa -out dkim-private.pem 2048 # Generate public key from private key openssl rsa -in dkim-private.pem -out dkim-public.pem -pubout -outform PEM
Expected output:
Generating RSA private key, 2048 bit long modulus .......+++ ........+++ e is 65537 (0x10001) writing RSA key
View Generated Keys
Linux/Mac/Git Bash:
cat dkim-private.pem cat dkim-public.pem
Windows PowerShell:
Get-Content dkim-private.pem
Get-Content dkim-public.pemExtract DNS TXT Record Value
You'll need to convert the public key into DNS TXT record format (without PEM headers).
Manual Extraction:
-
Open
dkim-public.pemin a text editor -
Copy only the key content (exclude the header/footer lines):
- Skip:
-----BEGIN PUBLIC KEY----- - Copy: The base64 content (multiple lines)
- Skip:
-----END PUBLIC KEY-----
- Skip:
- Remove all line breaks to create one continuous string
-
Prepend with DKIM tags:
v=DKIM1; k=rsa; p=
Example:
Original dkim-public.pem file:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890abcdefghij klmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklm nopqrstuvwxyz== -----END PUBLIC KEY-----
DNS TXT record value (for Step 4):
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz==
Command-Line Extraction (Optional):
Linux/Mac/Git Bash:
echo -n "v=DKIM1; k=rsa; p=" && grep -v "^-----" dkim-public.pem | tr -d '\n' && echo ""
Windows PowerShell:
Write-Host -NoNewline "v=DKIM1; k=rsa; p="
(Get-Content dkim-public.pem | Select-String -NotMatch "^-----") -join ""
Write-Host ""Save Keys for Next Steps
You'll need both keys in different formats:
For Step 2 (values.yaml):
- Save the complete
dkim-private.pemfile (including-----BEGIN/END PRIVATE KEY-----headers) - Save the complete
dkim-public.pemfile (including-----BEGIN/END PUBLIC KEY-----headers)
For Step 4 (DNS):
- Save the DNS TXT value created above (without headers)
Security Notes
- Never commit private keys to version control
- Store private keys in a secure location (password manager, secrets vault, etc.)
- Limit access to private keys to authorized personnel only
- You can safely share/publish the public key and DNS TXT value
Tip
You can integrate these OpenSSL commands into your own automation, scripts, or CI/CD pipelines as needed for your workflow.
Step 2: Update values.yaml
Verify Domain
standardConfig: primaryMailingDomain: yourdomain.com # Must match your email domain
Add Keys
appSecrets:
dkimSigning:
publicKey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890abcdefghij
klmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklm
nopqrstuvwxyz==
-----END PUBLIC KEY-----
privateKey: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVNjxyz1234567
890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
[... multiple lines ...]
-----END PRIVATE KEY-----Enable DKIM
additionalConfig:
dkimSigning:
enabled: true
selector: gw # Default; can customizeStep 3 (Optional): Configure Inbound FROM Address Rewrite
Note
This step is only required if running the gateway in inbound topology. Skip this step if using outbound topology.
What it does: Enables FROM address rewriting for inbound mail to support DKIM signing. When enabled, the gateway can properly sign inbound messages.
Update values.yaml
In your values.yaml file, verify or update the following in the additionalConfig section. Note that replaceFromEnabled defaults to "1" (enabled) in the Helm chart, so you only need to add this if you want to explicitly set or change the value:
additionalConfig: # Inbound FROM address rewrite # Enable or disable from address rewriting (inbound topology only) # This feature allows the Virtru Gateway to support DKIM # Values: # 1 - Enabled # 0 - Disabled # Default: 1 replaceFromEnabled: "1"
Values:
-
"1"= Enabled (default, recommended for DKIM support) -
"0"= Disabled
Step 4: Publish DNS Record
Create a TXT record in your DNS provider:
Record Details:
-
Type:
TXT -
Name/Host:
gw._domainkey.yourdomain.com -
Value:
v=DKIM1; k=rsa; p=MIIBIjAN...(from Step 1, remove all line breaks) -
TTL:
3600(or your default)
Note
DNS provider interfaces vary in field names (e.g., "Host" vs "Name" vs "Hostname") and format (some require the full FQDN, others only the subdomain). Refer to your DNS provider's documentation for TXT record creation.
Additional DNS Information
For broader context on DNS configuration for the Virtru Gateway, see Customer-Hosted Gateway DNS Entries.
Important:
- Remove ALL line breaks from the public key value
- Do NOT include
-----BEGIN/END-----headers in the DNS record - Some providers require splitting long values into multiple quoted sections
Verify DNS Propagation
dig gw._domainkey.yourdomain.com TXT +short # Should return: "v=DKIM1; k=rsa; p=..."
Step 5: Apply Configuration
# Upgrade Helm release (no downtime) helm upgrade gateway ./ -n virtru -f ./values.yaml # Wait for rollout kubectl rollout status statefulset/gateway-outbound-encrypt -n virtru
Step 6: Verify Configuration
# Set pod name
POD_NAME=$(kubectl get pods -n virtru -l app.kubernetes.io/name=gateway-outbound-encrypt -o jsonpath='{.items[0].metadata.name}')
# Check environment variable
kubectl exec -n virtru $POD_NAME -- printenv GATEWAY_DKIM_DOMAINS
# Expected: gw._domainkey.yourdomain.com
# Check secret exists
kubectl get secret gateway-dkim-secrets -n virtru
# Check keys mounted
kubectl exec -n virtru $POD_NAME -- ls -la /etc/opendkim/keys/
# Expected files:
# gw._domainkey.yourdomain.com.pem (private key)
# gw._domainkey.yourdomain.com-public.pem (public key)Note
If you're using a different gateway mode (e.g., inbound-decrypt), replace gateway-outbound-encrypt with the appropriate StatefulSet name. See the Gateway Mode Reference table below for the correct names.
Step 7: Test DKIM Signing
Send Test Email
# Get gateway IP
GATEWAY_IP=$(kubectl get svc gateway-outbound-encrypt -n virtru -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Send test
swaks --to test@gmail.com --from user@yourdomain.com --server $GATEWAY_IP --port 9001Verify Signature
In Gmail:
- Open email → ⋮ → Show original
- Look for:
DKIM-Signature: v=1; a=rsa-sha256; d=yourdomain.com; s=gw; - Scroll down to see:
DKIM: 'PASS'✓
Troubleshooting
No DKIM Signature in Emails
Quick checks:
# 1. Is DKIM enabled? helm get values gateway -n virtru | grep -A 2 dkimSigning # Should show: enabled: true # 2. Is env var set? kubectl exec -n virtru $POD_NAME -- printenv GATEWAY_DKIM_DOMAINS # Should output: gw._domainkey.yourdomain.com # 3. Are keys mounted? kubectl exec -n virtru $POD_NAME -- ls /etc/opendkim/keys/ # Should list two .pem files
If missing: Re-apply Helm chart with correct values.yaml
DKIM Verification Fails (dkim=fail)
Check DNS:
dig gw._domainkey.yourdomain.com TXT +short
Common issues:
- No output → DNS record not published
- Truncated → Value too long; split into quoted sections
-
Wrong selector → Must match
values.yamlselector
Pods Won't Start
# Check pod events kubectl describe pod $POD_NAME -n virtru # Common errors: # "FailedMount" → Secret not found # Solution: Verify gateway-dkim-secrets exists # Check secret kubectl get secret gateway-dkim-secrets -n virtru
Configuration Reference
Values.yaml Structure:
standardConfig:
primaryMailingDomain: yourdomain.com
additionalConfig:
dkimSigning:
enabled: true
selector: gw
appSecrets:
dkimSigning:
publicKey: |
-----BEGIN PUBLIC KEY-----
...
privateKey: |
-----BEGIN PRIVATE KEY-----
...Generated Resources:
- ConfigMap:
GATEWAY_DKIM_DOMAINS=gw._domainkey.yourdomain.com - Secret:
gateway-dkim-secretswith two .pem files - Mount:
/etc/opendkim/keys/
DNS Format:
<selector>._domainkey.<domain> TXT "v=DKIM1; k=rsa; p=<key>"
Gateway Mode Reference
Use the following table to identify the correct StatefulSet and Service names for your gateway deployment mode when running verification commands:
| Gateway Mode | StatefulSet Name | Service Name | Pod Label Selector |
|---|---|---|---|
| Outbound Encrypt | gateway-outbound-encrypt |
gateway-outbound-encrypt |
app.kubernetes.io/name=gateway-outbound-encrypt |
| Outbound Decrypt | gateway-outbound-decrypt |
gateway-outbound-decrypt |
app.kubernetes.io/name=gateway-outbound-decrypt |
| Inbound Encrypt | gateway-inbound-encrypt |
gateway-inbound-encrypt |
app.kubernetes.io/name=gateway-inbound-encrypt |
| Inbound Decrypt | gateway-inbound-decrypt |
gateway-inbound-decrypt |
app.kubernetes.io/name=gateway-inbound-decrypt |
| Outbound DLP | gateway-outbound-dlp |
gateway-outbound-dlp |
app.kubernetes.io/name=gateway-outbound-dlp |
| Inbound DLP | gateway-inbound-dlp |
gateway-inbound-dlp |
app.kubernetes.io/name=gateway-inbound-dlp |
Note
The names above assume a Helm release name of gateway. If you used a different release name, replace gateway with your actual release name (e.g., myrelease-outbound-encrypt).
Security Best Practices
✅ Use 2048-bit keys minimum (4096-bit for high security)
✅ Never commit private keys to version control
✅ Rotate keys annually
✅ Enable Kubernetes secrets encryption at rest
✅ Monitor DKIM pass rates via DMARC reports
Key Rotation:
- Generate new keys with different selector (e.g.,
gw2) - Publish new DNS record
- Update values.yaml and apply
- Wait 48 hours
- Remove old DNS record
Quick Reference
Essential commands from this guide:
# Generate DKIM keys (Step 1)
openssl genrsa -out dkim-private.pem 2048
openssl rsa -in dkim-private.pem -out dkim-public.pem -pubout
# Apply Helm configuration (Step 5)
helm upgrade gateway ./ -n virtru -f ./values.yaml
# Set pod name for verification commands
POD_NAME=$(kubectl get pods -n virtru -l app.kubernetes.io/name=gateway-outbound-encrypt -o jsonpath='{.items[0].metadata.name}')
# Verify DKIM environment variable (Step 6)
kubectl exec -n virtru $POD_NAME -- printenv GATEWAY_DKIM_DOMAINS
# Verify DNS record (Step 4)
dig gw._domainkey.yourdomain.com TXT +short
# Get gateway service IP (Step 7)
GATEWAY_IP=$(kubectl get svc gateway-outbound-encrypt -n virtru -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Send test email (Step 7)
swaks --to test@gmail.com --from user@yourdomain.com --server $GATEWAY_IP --port 9001Additional Resources
| Resource | Description |
|---|---|
| Reference: Kubernetes Variables | Complete mapping of Helm chart values to Gateway environment variables |
| Customer-Hosted Gateway DNS Entries | Comprehensive DNS configuration guide for Virtru Gateway |