Skip to main content

Tutorial: Configure Deny Rules with Domain Routing Using the Access Server CLI

Abstract

Configure deny rules with Domain Routing in OpenVPN Access Server using CLI. Learn how to block specific domains, combine allow and deny rules, and verify behavior using DNS proxy logs.

Overview

By default, Access Server supports allowlist rules for domain routing. This tutorial explains how to configure and validate deny rules using the CLI.

You can apply domain routing rules at different levels:

  • Globally (applies to all users).

  • Per Group.

  • Per individual user.

You can use one or more levels at the same time, depending on your access requirements.

Domain routing supports:

  • Exact domains (for example, example.com) — matches only that domain.

  • Wildcard domains (for example, *.example.com) — matches the domain and all subdomains.

  • Wildcard TLDs (for example, *.com) — matches all domains with that top-level domain (TLD).

Deny rules let you block traffic to specific domains, subdomains, or TLDs.

Prerequisites

  • Access Server 3.2.0 or newer.

  • Root privileges on your Access Server's console.

  • DNS server proxy enabled globally.

  • One or more users or groups configured.

Note

In our documentation, we use example IPv4 addresses and subnets reserved for documentation, such as 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.

Ensure you replace them with valid IPv4 addresses and subnets for your network(s).

Important

Before you begin, take note of the following:

  • Deny rules for domains are supported starting in Access Server 3.2.0.

  • You need to configure deny rules using the CLI.

  • Rule order matters. Deny rules need to come before allow rules when both apply.

Scenario 1: Deny access to a specific domain

In this scenario, you block access to app1.example.com for users connected to the VPN.

Tip

This scenario applies to both full and split tunneling.

  1. Connect to the console and get root privileges.

  2. Create a global ruleset:

    sacli --name "__DEFAULT___default_ruleset" --comment "Default ruleset for global domain rules" AccessControlRulesetsAdd
    • Example output:

      1
  3. Note this as the returned ruleset ID.

  4. Create a deny rule for app1.example.com:

    sacli --ruleset_id "1" --rule_type "domain_routing" --match_type "domain" --match_data "app1.example.com" --action "deny" --position "1" --comment "" AccessControlRulesAdd

    Parameter

    Value

    ruleset_id

    Use the value from the global ruleset output.

    match_type with domain

    Match an exact domain. For example, app1.example.com.

    match_type with domain_or_subdomain

    Match wildcard domains. For example, example.com.

    Tip

    Wildcard domain syntax differs between interfaces:

    • CLI: example.com

    • Admin Web UI: *.example.com

    match_data

    Domain to route.

    action with deny

    This will block the traffic to the domain.

    position

    Use the value for the next available position noted above.

  5. Attach the ruleset globally:

    sacli --user "__DEFAULT__" --position "0" --ruleset_id "1" AccessControlUserRulesetsAdd

    Tip

    Global rules use __DEFAULT__. Replace this value when applying rules to specific users or groups.

  6. Apply the changes:

    sacli start
  1. List rulesets:

    sacli AccessControlRulesetsList
    • Example output:

      [
        {
          "comment": "Default ruleset for global domain rules",
          "id": 1,
          "name": "__DEFAULT___default_ruleset",
          "owner": "__DEFAULT__",
          "owner_type": "user_default",
          "position": 0
        }
      ]
  2. Note the ruleset ID.

  3. List rules in the ruleset:

    sacli --ruleset_id '1'1 AccessControlRulesList

    1

    Replace the ruleset_id value with the value from the ruleset list output.

    • Example output:

      [
        {
          "action": "deny",
          "comment": "",
          "id": 1,
          "match_data": "app1.example.com",
          "match_type": "domain",
          "position": 0,
          "ruleset_id": 1,
          "type": "domain_routing"
        }
      ]
  4. Confirm the rule includes your deny list values. Example:

    • action: deny

    • match_data: app1.example.com

In this step, connect to the VPN as a user and generate traffic to app1.example.com to verify it's denied.

  1. Connect a VPN client.

  2. From the Access Server console, enable DNS proxy debug logging:

    echo "DEBUG_DNSPROXY=true" >> /usr/local/openvpn_as/etc/as.conf && systemctl restart openvpnas
  3. From the client, run:

    ping app1.example.com
    • Example output:

      Ping request could not find host app1.example.com. Please check the name and try again.
  4. On the server, run:

    grep "DNSPROXY" /var/log/openvpnas.log
    • Example output:

      2026-04-16T21:05:11+0000 [stdout#info] [DNSPROXY] OUT: '[335] Received query from 172.27.232.2:53380'
      2026-04-16T21:05:11+0000 [stdout#info] [DNSPROXY] OUT: '[335] Deny query for app1.example.com. IN A: domain is blocked'

    Tip

    Debug flags can generate large amounts of log data. After collecting the necessary information, remove the debug flag and restart Access Server to prevent excessive logging.

Scenario 2: Deny a specific domain and allow a wildcard

In this scenario:

  • Deny: app1.example.com

  • Allow: *.example.com

Tip

This scenario applies to both full and split tunneling.

  1. Connect to the console and get root privileges.

  2. Create a global ruleset:

    sacli --name "__DEFAULT___default_ruleset" --comment "Default ruleset for global domain rules" AccessControlRulesetsAdd
    • Example output:

      1
  3. Note this as the returned ruleset ID.

  4. Add rules to deny the specific domain and allow a wildcard domain:

    sacli --ruleset_id "1" --rule_type "domain_routing" --match_type "domain" --match_data "app1.example.com" --action "deny" --position "1" --comment "" AccessControlRulesAdd
    sacli --ruleset_id "1" --rule_type "domain_routing" --match_type "domain_or_subdomain" --match_data "example.com" --action "nat" --position "2" --comment "" AccessControlRulesAdd

    Parameter

    Value

    ruleset_id

    Use the value from the global ruleset output.

    match_type with domain

    Match an exact domain. For example, app1.example.com.

    match_type with domain_or_subdomain

    Match wildcard domains. For example, example.com.

    Tip

    Wildcard domain syntax differs between interfaces:

    • CLI: example.com

    • Admin Web UI: *.example.com

    match_data

    Domain to route.

    action with deny

    This will block the traffic to the domain.

    position

    Use the value for the next available position noted above.

  5. Attach the ruleset:

    sacli --user "__DEFAULT__" --position "0" --ruleset_id "1" AccessControlUserRulesetsAdd

    Tip

    Global rules use __DEFAULT__. Replace this value when applying rules to specific users or groups.

  6. Apply changes:

    sacli start
  1. List rulesets:

    sacli AccessControlRulesetsList
    • Example output:

      [
        {
          "comment": "Default ruleset for global domain rules",
          "id": 1,
          "name": "__DEFAULT___default_ruleset",
          "owner": "__DEFAULT__",
          "owner_type": "user_default",
          "position": 0
        }
      ]
  2. Note the ruleset ID.

  3. List rules in the ruleset:

    sacli --ruleset_id '1'1 AccessControlRulesList

    1

    Replace the ruleset_id value with the value from the ruleset list output.

    • Example output:

      [
        {
          "action": "deny",
          "comment": "",
          "id": 1,
          "match_data": "app1.example.com",
          "match_type": "domain",
          "position": 1,
          "ruleset_id": 1,
          "type": "domain_routing"
        },
        {
          "action": "nat",
          "comment": "",
          "id": 2,
          "match_data": "example.com",
          "match_type": "domain_or_subdomain",
          "position": 2,
          "ruleset_id": 1,
          "type": "domain_routing"
        }
      ]
  4. Confirm your rules are correct. From our example:

    • The deny rule is first.

    • The allow rule follows.

In this step, connect to the VPN as a user and generate traffic to app1.example.com to verify it's denied, while traffic to the rest of *.example.com is allowed.

  1. Connect a VPN client.

  2. From the Access Server console, enable DNS proxy debug logging:

    echo "DEBUG_DNSPROXY=true" >> /usr/local/openvpn_as/etc/as.conf && systemctl restart openvpnas
  3. From the client, run:

    ping app1.example.com
    • Example output:

      Ping request could not find host app1.example.com. Please check the name and try again.
  4. Test the allowed domain:

    ping app2.example.com
    • Example output:

      Reply from 100.64.0.1: bytes=32 time=116ms TTL=49
      Reply from 100.64.0.1: bytes=32 time=116ms TTL=49
      Reply from 100.64.0.1: bytes=32 time=114ms TTL=49
      Reply from 100.64.0.1: bytes=32 time=115ms TTL=49
  5. On the server, run:

    grep "DNSPROXY" /var/log/openvpnas.log
    • Example output:

      2026-04-16T21:16:41+0000 [stdout#info] [DNSPROXY] OUT: '[867] Received query from 172.27.236.2:60873'
      2026-04-16T21:16:41+0000 [stdout#info] [DNSPROXY] OUT: '[867] Forwarding query for app2.example.com. IN A'
      2026-04-16T21:16:41+0000 [stdout#info] [DNSPROXY] OUT: '[867] Received response'
      2026-04-16T21:16:41+0000 [stdout#info] [DNSPROXY] OUT: '[867] Changed address for app2.example.com: 192.0.2.1 -> 100.64.0.1'

    Tip

    Debug flags can generate large amounts of log data. After collecting the necessary information, remove the debug flag and restart Access Server to prevent excessive logging.