Home Unbound Views in OPNsense to Resolve Domains by VLAN/Subnet
Post
Cancel

Unbound Views in OPNsense to Resolve Domains by VLAN/Subnet

Configure Unbound DNS in OPNsense for Subnet Based Domain Resolution

Running multiple VLANs in your home or lab can be a headache — especially when it comes to DNS. If you’ve got a server with interfaces on multiple VLANs/subnets, OPNsense’s Unbound DNS doesn’t respond automatically based on the interface or subnet. By default, it gives clients all the IPs it knows for that hostname.

That means your gaming PC on VLAN 112 might resolve unraid.myhouse.home.arpa to an IP on VLAN 88, which won’t work.


Unbound Views

Unbound has a feature called views. With views, you can tell Unbound:

  • “If a client comes from subnet 192.168.112.0/21, use this view.”

  • “If a client comes from subnet 192.168.88.0/21, use that view.”

Each view can have its own DNS records, so the same hostname can return different IPs depending on the source VLAN.

For example:

  • Clients on VLAN 112 → unraid.myhouse.home.arpa = 192.168.112.12

  • Clients on VLAN 88 → unraid.myhouse.home.arpa = 192.168.88.12

This solves DNS leaking all IPs problem once and for all.


Unbound Views Script

If you only have one or two VLANs, you could hand-write the Unbound config file with views. But if you’ve got multiple VLANs and several hosts, the config quickly becomes tedious and error-prone.

That’s why I built a script to do it automatically. Instead of learning Unbound’s config syntax line-by-line, you just define your VLANs and hosts in a simple text file — and the script generates the correct configuration for OPNsense.

You can find the — 🔭 generate-unbound-views.sh script — script below.


Using DNS Views

When running multiple VLANs and subnets in OPNsense, you may need Unbound DNS to return different IP addresses depending on the interface or client network.

For example:

  • You have a host unraid with addresses on multiple VLANs.

  • You want clients on VLAN 112 (192.168.112.0/21) to resolve unraid.myhouse.home.arpa to 192.168.112.12.

  • You want clients on VLAN 88 (192.168.88.0/21) to resolve that same hostname to 192.168.88.12.

By default, OPNsense/Unbound will happily return all addresses for unraid to everyone — which isn’t what we want.

This is where views come in. PowerDNS, some CISCO stuff, and BIND all have views available. Infact, BIND introduced views. BIND is available in OPNsense, but today we’re just covering Unbound.


The Problem with Old Documentation

If you’ve searched forums or blogs, you’ve probably seen instructions like:

“Paste your custom config under Services → Unbound DNS → Advanced.”

That’s no longer possible. As of OPNsense 21.1, the custom configuration fields were removed (announcement here).

New Production Friendly Method

The new approach is to place your Unbound config snippets into:

1
/usr/local/etc/unbound.opnsense.d/

That’s documented in the OPNsense manual, but it doesn’t explain how to use Unbound views.For that, you need the upstream Unbound docs:


Step-by-Step: Configuring Views

We’re going to tell Unbound:

  • Which subnets belong to which views

  • What local records each view should return


Instructions will walk through setting this up with two VLANs:

  • VLAN 112: 192.168.112.0/21unraid = 192.168.112.12

  • VLAN 88: 192.168.88.0/21unraid = 192.168.88.12


1. SSH into OPNsense and install an editor

1
pkg install nano

2. Create a custom Unbound config file

1
nano /usr/local/etc/unbound.opnsense.d/vlan-views.conf

3. Add the configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Server defines which subnets use which views
server:
    access-control-view: 192.168.112.0/21 "vlan112_view"
    access-control-view: 192.168.88.0/21 "vlan88_view"

# View for VLAN 112 (Gaming)
view:
    name: "vlan112_view"
    local-zone: "myhouse.home.arpa." transparent
    local-data: "unraid.myhouse.home.arpa. IN A 192.168.112.12"

# View for VLAN 88 (Work)
view:
    name: "vlan88_view"
    local-zone: "myhouse.home.arpa." transparent
    local-data: "unraid.myhouse.home.arpa. IN A 192.168.88.12"

🔎 Replace myhouse.home.arpa with your own local domain.

4. Validate the config

1
configctl unbound check

5. Restart Unbound

1
configctl unbound restart

(Optional: reboot if you want to be extra sure it persists.)

6. Verify

The config won’t appear in /var/unbound/unbound.conf directly — that file is generated dynamically. But your custom file will be included automatically.

Check with:

1
2
dig @192.168.112.254 unraid.myhouse.home.arpa
dig @192.168.88.254 unraid.myhouse.home.arpa

Clients in VLAN 112 should get 192.168.112.12, while VLAN 88 clients should get 192.168.88.12.


Now you can:

  • Control DNS responses per subnet/VLAN

  • Keep multiple interfaces for the same hostname cleanly separated

  • Avoid Unbound’s default behavior of returning all addresses

This is extremely useful if you’re running a host that has multiple IPs across different networks, but you want clients to resolve to the “closest” or correct one automatically.


Automating Unbound Views with a Script

Doing this by hand works fine for one or two VLANs. But if you have several VLANs and multiple hosts (unraid, nas, plex…), editing configs quickly becomes painful.

That’s why I wrote a script — 🔭 generate-unbound-views.sh script — that automates everything.


How It Works

The script takes an input file where each line is:

VLAN_ID,SUBNET,HOSTNAME,IP_ADDRESS

For example:

# Gaming VLAN
112,192.168.112.0/21,unraid,192.168.112.12
112,192.168.112.0/21,nas,192.168.112.15

# Work VLAN
88,192.168.88.0/21,unraid,192.168.88.12
88,192.168.88.0/21,nas,192.168.88.15

# Name Your Untagged Networks (no VLAN #)
kids,10.0.0.0/24,zigbee_light,10.0.0.10
kids,10.0.0.0/24,fedorasugar,10.0.0.20

Run the script, and it generates the full Unbound config.


Running the Script

Step 1. Download the generate-unbound-views.sh script

Copy and paste the script to your OPNsense box, adding execute privs:

1
2
nano /root/generate-unbound-views.sh
chmod +x /root/generate-unbound-views.sh

Step 2. VLAN_ID,SUBNET,HOSTNAME,IP_ADDRESS

Create your VLAN definitions in a file:

1
nano /root/vlans.txt

Step 3. Use the script and your input file

Generate the Unbound config with:

  • input_file - vlans.txt

  • output_file - /usr/local/etc/unbound.opnsense.d/vlan-views.conf

  • domain_suffix - mycustom.domain.fantastic

1
./generate-unbound-views.sh vlans.txt /usr/local/etc/unbound.opnsense.d/vlan-views.conf mycustom.domain.fantastic

Step 4. Does it still work

Validate and restart Unbound:

1
2
configctl unbound check
configctl unbound restart

Step 5. Did you change anything

Test resolution from each VLAN:

1
dig unraid

Clients on VLAN 112 will see the .112.12 address, while VLAN 88 clients will see .88.12.


generate-unbound-views.sh script

Remember: Put all of your VLANs, Subnets, hostnames, IPs in a simple comma seperated text file for input.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
#!/bin/sh
#
##########################################################
# generate-unbound-views.sh
#
# Purpose: Generate Unbound VLAN-specific DNS views configuration from an input file
#
# Example Usage:
#   ./generate-unbound-views.sh input.txt output.conf "mydomain.local"
#
# Arguments:
#   input_file   - CSV file containing VLAN definitions (required)
#   output_file  - The output configuration file (default: /usr/local/etc/unbound.opnsense.d/vlan-views.conf)
#   domain_suffix - The domain to use (default: "myhouse.home.arpa")
#
#
# Input file format are comma seperated values (CSV):
#    VLAN_ID,SUBNET,HOSTNAME,IP_ADDRESS
#
# Example input.txt:
#    # IoT VLAN 112 - 192.168.112.0/24
#    112,192.168.112.0/24,smart_washer,192.168.112.12
#    112,192.168.112.0/24,security_camera,192.168.112.15
#
#    # Guest VLAN 88 - 192.168.88.0/24
#    88,192.168.88.0/24,scanner,192.168.88.10
#    88,192.168.88.0/24,printer,192.168.88.20
#
#    # Systems Untagged VLAN - 192.168.0.0/24
#    systems,192.168.0.0/24,router,192.168.0.10
#    systems,192.168.0.0/24,nas,192.168.0.20
#
#    # KidsNet Untagged VLAN - 10.0.0.0/24
#    kids,10.0.0.0/24,zigbee_light,10.0.0.10
#    kids,10.0.0.0/24,fedorasugar,10.0.0.20
#
#
##########################################################
set -e  # Exit on any error

# ============================================================================
# Configuration
# ============================================================================
DEFAULT_DOMAIN_SUFFIX="myhouse.home.arpa"
DEFAULT_OUTPUT="/usr/local/etc/unbound.opnsense.d/vlan-views.conf"

# ============================================================================
# Functions
# ============================================================================

usage() {
    cat << EOF
Usage: $0 <input_file> [output_file] [domain_suffix]

Generate Unbound VLAN-specific DNS views configuration.

Arguments:
    input_file   - CSV file containing VLAN definitions (required)
    output_file  - The output configuration file (default: /usr/local/etc/unbound.opnsense.d/vlan-views.conf)
    domain_suffix - The domain to use (default: "myhouse.home.arpa")

Input file format are comma seperated values (CSV):
    VLAN_ID,SUBNET,HOSTNAME,IP_ADDRESS

Example input file:
    # IoT VLAN 112 - 192.168.112.0/24
    112,192.168.112.0/24,smart_washer,192.168.112.12
    112,192.168.112.0/24,security_camera,192.168.112.15

    # Guest VLAN 88 - 192.168.88.0/24
    88,192.168.88.0/24,scanner,192.168.88.10
    88,192.168.88.0/24,printer,192.168.88.20

    # Systems Untagged VLAN - 192.168.0.0/24
    systems,192.168.0.0/24,router,192.168.0.10
    systems,192.168.0.0/24,nas,192.168.0.20

    # KidsNet Untagged VLAN - 10.0.0.0/24
    kids,10.0.0.0/24,zigbee_light,10.0.0.10
    kids,10.0.0.0/24,fedorasugar,10.0.0.20

Example usage:
    $0 vlans.txt
    $0 vlans.txt /tmp/test-views-output.conf
    $0 vlans.txt <output_file_required_if_using_custom_domain> mycustom.domain.fantastic

Validate it works:
    configctl unbound check    # Validate configuration
    configctl unbound restart  # Apply changes
EOF
    exit 1
}

log_info() {
    echo "[INFO] $*" >&2
}

log_error() {
    echo "[ERROR] $*" >&2
}

validate_input() {
    local input_file="$1"
    
    if [ ! -f "$input_file" ]; then
        log_error "Input file not found: $input_file"
        exit 1
    fi
    
    if [ ! -r "$input_file" ]; then
        log_error "Input file not readable: $input_file"
        exit 1
    fi
    
    # Check if file has any valid data lines
    if ! grep -qE '^[^#]' "$input_file" 2>/dev/null; then
        log_error "Input file contains no valid data (only comments or empty)"
        exit 1
    fi
}

# ============================================================================
# Main Script
# ============================================================================

# Check arguments
if [ $# -lt 1 ]; then
    usage
fi

INPUT_FILE="$1"
OUTPUT_FILE="${2:-$DEFAULT_OUTPUT}"
DOMAIN_SUFFIX="${3:-$DEFAULT_DOMAIN_SUFFIX}"

log_info "Input file: $INPUT_FILE"
log_info "Output file: $OUTPUT_FILE"
log_info "Domain suffix: $DOMAIN_SUFFIX"

# Validate input file
validate_input "$INPUT_FILE"

# Create temporary file for processing
TMP_FILE=$(mktemp) || {
    log_error "Failed to create temporary file"
    exit 1
}

# Ensure temp file cleanup on exit
trap 'rm -f "$TMP_FILE"' EXIT INT TERM

# ============================================================================
# Parse input and build data structures
# ============================================================================
log_info "Parsing input file..."

# Arrays to store unique VLANs and their data
# Format: vlan_id:subnet:hostname1:ip1:hostname2:ip2...
VLAN_DATA=""
UNTAGGED_COUNT=0  # Counter for untagged views

while IFS=',' read -r vlan_id subnet hostname ip_address; do
    # Skip comments and empty lines
    echo "$vlan_id" | grep -qE '^\s*#' && continue
    echo "$vlan_id" | grep -qE '^\s*$' && continue
    
    # Trim whitespace
    vlan_id=$(echo "$vlan_id" | tr -d ' \t')
    subnet=$(echo "$subnet" | tr -d ' \t')
    hostname=$(echo "$hostname" | tr -d ' \t')
    ip_address=$(echo "$ip_address" | tr -d ' \t')
    
    # Validate fields
    if [ -z "$vlan_id" ] || [ -z "$subnet" ] || [ -z "$hostname" ] || [ -z "$ip_address" ]; then
        log_error "Invalid line (missing fields): $vlan_id,$subnet,$hostname,$ip_address"
        continue
    fi
    
    # Check if vlan_id is a number or not
    if ! echo "$vlan_id" | grep -qE '^[0-9]+$'; then
        # If not a number, treat as a custom name for untagged VLAN
        UNTAGGED_COUNT=$((UNTAGGED_COUNT + 1))
        VIEW_NAME="${vlan_id}_${UNTAGGED_COUNT}"
    else
        # For numeric VLAN_ID, use the standard naming convention
        VIEW_NAME="vlan${vlan_id}_view"
    fi
    
    # Store data - append to existing VLAN or create new entry
    if echo "$VLAN_DATA" | grep -q "^$vlan_id:$subnet:"; then
        # Append to existing VLAN
        VLAN_DATA=$(echo "$VLAN_DATA" | sed "s|^$vlan_id:$subnet:\(.*\)$|$vlan_id:$subnet:\1:$hostname:$ip_address|")
    else
        # New VLAN entry
        if [ -z "$VLAN_DATA" ]; then
            VLAN_DATA="$vlan_id:$subnet:$hostname:$ip_address"
        else
            VLAN_DATA="$VLAN_DATA
$vlan_id:$subnet:$hostname:$ip_address"
        fi
    fi
    
done < "$INPUT_FILE"

if [ -z "$VLAN_DATA" ]; then
    log_error "No valid data parsed from input file"
    exit 1
fi

# ============================================================================
# Generate configuration file
# ============================================================================
log_info "Generating Unbound configuration..."

cat > "$TMP_FILE" << 'EOF_HEADER'
# Unbound VLAN-specific DNS Views Configuration
# Generated by generate-unbound-views.sh
# DO NOT EDIT MANUALLY - Regenerate using the script
#
# Purpose: Return VLAN-specific IP addresses based on which subnet
# the DNS query originates from.

# ============================================================================
# SERVER CLAUSE
# ============================================================================
# This maps source subnets to their respective views
# Format: access-control-view: <subnet> "<view_name>"  (view names MUST be quoted)
server:
    
EOF_HEADER

# Generate access-control-view entries
echo "$VLAN_DATA" | while IFS=':' read -r vlan_id subnet rest; do
    if ! echo "$vlan_id" | grep -qE '^[0-9]+$'; then
        # For non-numeric VLAN IDs, use the custom name and increment the counter
        UNTAGGED_COUNT=$((UNTAGGED_COUNT + 1))
        VIEW_NAME="${vlan_id}_${UNTAGGED_COUNT}"
        cat >> "$TMP_FILE" << EOF
    # Custom Untagged VLAN - ${subnet}
    access-control-view: ${subnet} "${VIEW_NAME}"

EOF
    else
        cat >> "$TMP_FILE" << EOF
    # VLAN ${vlan_id} - ${subnet}
    access-control-view: ${subnet} "vlan${vlan_id}_view"
    
EOF
    fi
done

cat >> "$TMP_FILE" << 'EOF_MID'

# ============================================================================
# VIEWS - One per VLAN
# ============================================================================
EOF_MID

# Generate view entries
echo "$VLAN_DATA" | while IFS= read -r line; do
    [ -z "$line" ] && continue
    
    # Extract vlan_id and subnet (first two colon-separated fields)
    vlan_id=$(echo "$line" | cut -d: -f1)
    subnet=$(echo "$line" | cut -d: -f2)
    
    if ! echo "$vlan_id" | grep -qE '^[0-9]+$'; then
        # For non-numeric VLAN IDs, use the custom name and increment the counter
        UNTAGGED_COUNT=$((UNTAGGED_COUNT + 1))
        VIEW_NAME="${vlan_id}_${UNTAGGED_COUNT}"
        cat >> "$TMP_FILE" << EOF
# ============================================================================
# VIEW - untagged network ${VIEW_NAME}
# ============================================================================
view:
    name: "${VIEW_NAME}"
    local-zone: "${DOMAIN_SUFFIX}." transparent
EOF
    else
        cat >> "$TMP_FILE" << EOF
# ============================================================================
# VIEW FOR VLAN ${vlan_id}
# ============================================================================
view:
    name: "vlan${vlan_id}_view"
    local-zone: "${DOMAIN_SUFFIX}." transparent
EOF
    fi
    
    # Parse and add all hostnames for this VLAN
    field_num=3
    num_fields=$(echo "$line" | awk -F: '{print NF}')
    
    while [ $field_num -le $num_fields ]; do
        hostname=$(echo "$line" | cut -d: -f${field_num})
        next_field=$((field_num + 1))
        
        if [ $next_field -le $num_fields ]; then
            ip=$(echo "$line" | cut -d: -f${next_field})
            
            if [ -n "$hostname" ] && [ -n "$ip" ]; then
                cat >> "$TMP_FILE" << EOF
    local-data: "${hostname}.${DOMAIN_SUFFIX}. IN A ${ip}"
EOF
            fi
        fi
        
        field_num=$((field_num + 2))
    done
    
    echo "" >> "$TMP_FILE"
done

# Add footer with instructions
cat >> "$TMP_FILE" << 'EOF_FOOTER'

# ============================================================================
# VALIDATION & RESTART
# ============================================================================
# After editing the input file and regenerating:
# 1. Validate: configctl unbound check
# 2. Restart:  configctl unbound restart
# 3. Test:     nslookup <hostname>.<domain> (from each VLAN)
# ============================================================================
EOF_FOOTER

# ============================================================================
# Install the configuration
# ============================================================================

# Check if output directory exists
OUTPUT_DIR=$(dirname "$OUTPUT_FILE")
if [ ! -d "$OUTPUT_DIR" ]; then
    log_error "Output directory does not exist: $OUTPUT_DIR"
    log_error "This script is intended for OPNsense with Unbound installed"
    exit 1
fi

# Backup existing file if it exists
if [ -f "$OUTPUT_FILE" ]; then
    BACKUP_FILE=".${OUTPUT_FILE}.backup.$(date +%Y%m%d_%H%M%S).old"
    log_info "Backing up existing configuration to: $BACKUP_FILE"
    cp "$OUTPUT_FILE" "$BACKUP_FILE"
fi

# Move temp file to final location
mv "$TMP_FILE" "$OUTPUT_FILE"
chmod 644 "$OUTPUT_FILE"

log_info "Configuration generated successfully: $OUTPUT_FILE"
log_info ""
log_info "Next steps:"
log_info "  1. Review the configuration: cat $OUTPUT_FILE"
log_info "  2. Validate: configctl unbound check"
log_info "  3. Apply: configctl unbound restart"
log_info ""

# Summary of what was generated
VLAN_COUNT=$(echo "$VLAN_DATA" | wc -l | tr -d ' ')
log_info "Summary:"
log_info "  - VLANs configured: $VLAN_COUNT"
log_info "  - Domain suffix: $DOMAIN_SUFFIX"

echo "$VLAN_DATA" | while IFS=':' read -r vlan_id subnet rest; do
    # Count hostnames (every other field after subnet)
    host_count=$(echo "$rest" | awk -F: '{print int((NF+1)/2)}')
    log_info "  - VLAN $vlan_id ($subnet): $host_count host(s)"
done

exit 0


More Unbound View Examples

Let me give another example and expand on these views a bit more, based on the concept here: https://forum.opnsense.org/index.php?topic=40247.0

You can fine grain redirect specific subnets, for example work people cannot use Copilot or watch restricted YouTube content:

# GLOBAL: Define which subnets get which views
server:
    access-control-view: 192.168.112.0/21 "vlan112_view"
    access-control-view: 192.168.88.0/21 "vlan88_view"

# VIEW: VLAN 112 (Gaming) - No filters
view:
    name: "vlan112_view"
    local-zone: "myhouse.home.arpa." transparent
    local-data: "unraid.myhouse.home.arpa. IN A 192.168.112.12"

# VIEW: VLAN 88 (Work) - With redirect filters
view:
    name: "vlan88_view"
    local-zone: "myhouse.home.arpa." transparent
    local-data: "unraid.myhouse.home.arpa. IN A 192.168.88.12"

    # Bing
    local-zone: "bing.com" redirect
    local-data: "bing.com CNAME nochat.bing.com"

    # CoPilot
    local-zone: "copilot.microsoft.com" redirect
    local-data: "copilot.microsoft.com CNAME cdp.copilot.microsoft.com"

    # YouTube
    local-zone: "www.youtube.com" redirect
    local-data: "www.youtube.com CNAME restrict.youtube.com"
    local-zone: "m.youtube.com" redirect
    local-data: "m.youtube.com CNAME restrict.youtube.com"
    local-zone: "youtubei.googleapis.com" redirect
    local-data: "youtubei.googleapis.com CNAME restrict.youtube.com"
    local-zone: "youtube.googleapis.com" redirect
    local-data: "youtube.googleapis.com CNAME restrict.youtube.com"
    local-zone: "www.youtube-nocookie.com" redirect
    local-data: "www.youtube-nocookie.com CNAME restrict.youtube.com"

UnRAID Interface Custom SSH ListenAddress

UnRAID is real real real aggressive with setting up what interfaces should or should not be able to allow connections on an open port 22. One major issue is the sshd_build() function in Unraid’s startup scripts. This function automatically detects network interfaces and modifies SSH settings without your input. While this might be useful for some users, it becomes a hassle when you want to manually configure your system.

  • Problem: If you change your sshd_config file, it will almost certainly overwrite it.

  • Solution: You must disable sshd and manage this service/file yourself.

The script below will fix this, please follow the instructions.

The only area needs editing is the sed section that adds everything after AddressFamily inet.

You can add up to 16 interface addresses, just be sure there is a \ at the end of all the lines but the last.


Edit UnRAID Custom SSH ListenAddress Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/bash
# #  From: https://forums.unraid.net/topic/165993-custom-ssh-listenaddress/#findComment-1561729
#
# #  Step 1. disable SSH in UnRAID GUI:
#
# #  Settings → Management Access → SSH
#
# #  Step 2. create this user script:
#
# Log the start of the script execution
logger "SSH script execution started."
#
sleep 30
#
# Script part 1. Edit your config with ONLY the interfaces you want
# # NOTE: 16 - 5 = 11 Interfaces left
sudo sed -i '/^[[:space:]]*#\?\s*ListenAddress/d' /boot/config/ssh/sshd_config && \
sudo sed -i '/AddressFamily inet/a\
ListenAddress 192.168.2.12\
ListenAddress 192.168.3.12\
ListenAddress 10.236.88.12\
ListenAddress 10.236.112.12\
ListenAddress 10.0.0.12' /boot/config/ssh/sshd_config
#
sleep 20
#
# Script part 2. Copy it to the running location
cp /boot/config/ssh/sshd_config /etc/ssh/sshd_config
#
# Script part 3. Make sure your host keys exist
ssh-keygen -A
#
# Script part 4. Make sure it's started
sleep 10
#
# Script part 5. Start sshd directly, bypassing the UnRAID script
/usr/sbin/sshd
#
# Log the completion of the script execution
logger "SSH Script execution completed."
#
##########################################
## Tools to check if you got this working:
#
### Check if there's another config file:
sshd -T | grep listenaddress
#
### See where modifications are:
grep -r "ListenAddress" /etc/ssh/
grep -r "ListenAddress" /boot/config/
#
### Check syslog for any SSH-related errors:
cat /var/log/syslog | grep sshd

This post is licensed under CC BY 4.0 by the author.