Features | Pricing | Documentation | Contact | Blog

Run Your Own Serverless DNS

By Lee Harding | July 1, 2025 | 10 min read

DNS filtering is an important service for schools, enterprises, ISPs, Wi-Fi providers and others needing to protect users and networks from undesirable content. Custom DNS can also enable offering premium features like vanity domains to customers of multi-tenant SaaS applications. Traditional DNS service options typically fall into two categories: self-hosting DNS servers (maintenance-heavy) or subscribing to a commercial DNS filtering service that charges per user per month (simple but limited and expensive at scale).

The "third option", building in-house, may have previously been off the radar because of the expertise needed and expense of developing such software. But what if that changed? What if your organization could build a custom DNS filtering service with minimal effort that's both easy to maintain and doesn't charge high per-user fees?

In this article, I'll walk through the dns-filter example that leverages Proxylity UDP Gateway together with AWS API Gateway to create a bespoke DNS filtering service supporting both UDP based DNS and the newer DNS over HTTPS used in browsers. You'll see how to deploy it, how to customize it, and why building may make more sense today than self-hosting on premises or buying.

What the DNS Filter Does

Before diving into deployment, let's understand what this example provides:

The DNS filter architecture separates concerns: Proxylity UDP Gateway handles traditional DNS network communication, while code running in your AWS account (Lambda functions) handles the filtering logic and a DynamoDB table stores configuration. It's a simple and reliable serverless approach.

Deploying in Your Own AWS Account

The deployment of the example is straightforward and can be completed in a few minutes. Here's what you'll need:

Let's walk through the deployment steps:

Step 1: Clone the Examples Repository

git clone https://github.com/proxylity/examples.git
cd examples/dns-filter

Step 2: Configure the Deployment

Run scripts/prerequisites.sh to select or create buckets needed for the deployment process. For each of the three regions that will host handlers for the dns-filter example, you'll need a bucket with the name pattern ${DEPLOY_BUCKET_NAME_PREFIX}${REGION}. This script creates those buckets if you don't already have suitable choices.

You can optionally distable logging for maximum privacy, and associate a dommain name to the service. See the file scripts/configure.sh> for more information on the options available.

Step 3: Deploy the Solution

With your configuration set, deployment is a single command:

AWS_REGION=us-west-2 ./scripts/deploy.sh

The script does several things:

  1. Deploys the global resources (DynamoDB table, IAM roles, UDP Gateway listener) to us-west-2
  2. Captures the outputs from the global stack into a JSON file for reference
  3. Builds the Lambda functions that will process DNS requests
  4. Deploys the Lambda functions and other resources to each region

After deployment completes, you'll have a working DNS filtering service. Let's extract some key information from the outputs JSON to make testing easier:

export DNS_DOMAIN=$(jq -r .Domain global-stack-outputs.json)
export DNS_PORT=$(jq -r .Port global-stack-outputs.json)
export DNS_ENDPOINT=$(jq -r .Endpoint global-stack-outputs.json)
export DNS_TABLE=$(jq -r .TableName global-stack-outputs.json)

Step 4: Add Domains to Block or Redirect

Initially, your DNS filter will allow all domains since no blocking rules exist in the DynamoDB table. To manually add domains, you can use the AWS CLI or DynamoDB console. For example, to block a specific domain:

aws dynamodb put-item \
  --table-name $DNS_TABLE \
  --item '{"PK": {"S": "cheese.com"}, "SK": {"S": "cheese.com"}, \
    "blocked": {"BOOL": true}}'

To redirect a domain to a specific IP address:

aws dynamodb put-item \
  --table-name $DNS_TABLE \
  --item '{"PK": {"S": "portal.example.com"}, \
    "SK": {"S": "portal.example.com"}, \
    "redirect": {"S": "10.0.0.1"} }'

Step 5: Use Your DNS Filter

Once deployed, you can test UDP DNS filtering using nslookup or dig:

# Should resolve normally
nslookup -port=${DNS_PORT} google.com ${DNS_DOMAIN}

# Should return NXDOMAIN if in the block list
nslookup -port=${DNS_PORT} cheese.com ${DNS_DOMAIN}

In your browser the DNS filtering will apply as well. You can test it by configuring your DNS over HTTPS (DoH) provider in settings to use the API Gateway endpoint in the outputs of each region's stack (or the domain name you provided, in the configuration, if any).

Customizing Your DNS Filter

The power of the serverless approach to DNS filtering is how easily you can customize and extend the filtering logic. The Lambda functions respond to DNS requests and makes decisions based on data in DynamoDB. Here are some ways you could extend it:

Time-Based Access

Allow certain domains only during specific hours (e.g., social media only during breaks). Modify the Lambda to look for `available_hours` on the DDB entries and modify responses to return the unroutable address outside those hours. Adjust the answer TTL to match.

Network CIDR Blocking

Block access to certain IP ranges based on CIDR to prevent bad actors from using different domain names to drive users to the same malicious servers.

Split Horizons

Apply different lookup and filtering rules based on the source IP. Lookups from public IPs can return different results than lookups from behind your enterprise firewall.

Branded/Dynamic Subdomains

Lookup subdomains for your multi-tenant customers from your application database in real-time. Combine that with region and subscription data to determine what IPs to return.

Logging & Analytics

Track DNS requests for security analysis or usage patterns while keeping it secure and private. Use CloudWatch Logs to capture request metadata and create dashboards for insights.

Don't have development resources? AI coding assistants can help generate the code for these customizations with minimal guidance. For example, you could ask an LLM to:

The serverless architecture makes these customizations straightforward -- there's no need to reconfigure network interfaces, manage scaling groups, or worry about high availability. The infrastructure handles all of that automatically.

Cost Comparison: Bespoke DNS vs. Traditional Options

Let's compare three approaches to DNS filtering for an organization with 1,000 users:

Approach Setup Effort Maintenance Customization Monthly Cost
Self-Hosted DNS Servers High (days to weeks) High (patching, scaling, redundancy) Limited to software options ~$500 (hardware, personnel)
Per-User DNS Service Low (hours) Low (managed service) Limited to provider options ~$3,000 ($3/user/month)
Proxylity Bespoke DNS Low (minutes) None (serverless) Unlimited and simple ~$100-200 (usage-based)

The key insight: traditional DNS filtering services charge per user in pursuit of "value based pricing". But your usage may not scale with users. With a serverless architecture your costs scale with actual usage, not user count. This means:

This approach becomes even more economical as your user base grows. Providing a 10,000-user organization with DNS filtering might cost thousands of dollars per month with typical per-user pricing, but only $500-1,000/month with this serverless approach.

Maintenance: Almost None Required

Traditional DNS servers require regular maintenance:

With the serverless DNS filter, maintenance is virtually eliminated :

The only "maintenance" might be occasionally updating your blocklist or modifying filtering rules -- both of which are simple DynamoDB operations that can be automated.

The End of Per-User Pricing for Network Services

This DNS filtering example represents a broader shift in how network services should be built and priced. The traditional model of charging per user made sense when:

  1. Services were difficult to build and maintain yourself
  2. Servers were provisioned per customer and sized based on expected volume
  3. Customization was a luxury few needed

None of these conditions hold true anymore. With coding assistants, serverless architectures and services like Proxylity UDP Gateway:

  1. Building custom network services takes minutes to hours, not weeks
  2. Costs scale with actual usage, not arbitrary user counts
  3. Customization is both easy and essential for competitive advantage

Why pay $3-5 per user per month (or more) for DNS filtering that doesn't quite meet your needs when you can build exactly what you want for a fraction of the cost?

The same principle applies beyond DNS to RADIUS authentication, NTP services, custom VPN endpoints, and more. The future of network services isn't buying expensive, one-size-fits-most solutions -- it's building bespoke services that perfectly match your requirements without the traditional complexity.

To get started with your own DNS filter, check out our example code on GitHub, or explore our documentation to learn more about what's possible with serverless UDP.

Ready to build your own bespoke DNS service?

Get started with Proxylity UDP Gateway today. No upfront costs – pay only for what you use.

Buy with AWS View DNS Filter Example