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:
- Domain blocking - Return "non-existent name" in response to queries for unwanted domains
- Domain redirection - Redirect specific domains to custom IP addresses (for captive portals, or to "black hole" connections to them, for example)
- High Availability and Resilience - Deployed across multiple AWS regions so users experience minimal downtime and low latency
- Unlimited scaling - Handle any level of traffic from zero to millions of requests, and never worry about capacity planning
- Simple domain management - Configuration stored in a globally-replicated DynamoDB table benefiting from Amazon's 99.999% availability SLA and recently announced zero Recovery Point Objective capability
- Privacy and control - You control what data is stored and logged, where it is stored (region), and who can see and use it (unlike commercial DNS providers)
- Low cost - As low as $1 per month (total) with light to moderate use.
The DNS filter architecture separates concerns: Proxylity UDP Gateway handles traditional DNS network
communication,
while code running in
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:
- An AWS account with access to create CloudFormation stacks
- A subscription to Proxylity UDP Gateway through AWS Marketplace
- The
aws
CLI,git
,jq
and Thedotnet
SDK installed - Basic knowledge of bash scripting (for running the deployment scripts)
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:
- Deploys the global resources (DynamoDB table, IAM roles, UDP Gateway listener) to us-west-2
- Captures the outputs from the global stack into a JSON file for reference
- Builds the Lambda functions that will process DNS requests
- 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:
- "Add time-based filtering to the DNS Lambda function"
- "Implement IP-based filtering groups for the DNS filter example"
- "Create a CloudWatch dashboard for DNS filter usage statistics"
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:
- No user-based pricing - You pay for the queries you process, not how many people might use the system
- No idle capacity costs - When usage drops (nights, weekends, holidays), so do your costs
- Sub-linear cost scaling - Costs grow in decreasing proportion to usage because of batching
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:
- Security patches and OS updates
- Monitoring and alerting setup
- Backup and recovery planning
- Capacity planning and scaling
- High availability configuration
With the serverless DNS filter, maintenance is virtually eliminated :
- No servers to patch or update
- Automatic scaling from zero to millions of requests
- Built-in multi-region redundancy
- No disk space to manage or backups to schedule
- AWS and Proxylity handle the underlying infrastructure
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:
- Services were difficult to build and maintain yourself
- Servers were provisioned per customer and sized based on expected volume
- Customization was a luxury few needed
None of these conditions hold true anymore. With coding assistants, serverless architectures and services like Proxylity UDP Gateway:
- Building custom network services takes minutes to hours, not weeks
- Costs scale with actual usage, not arbitrary user counts
- 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.