AdGuard for iOS: a Safari content blocker that also runs its own DNS
The most advanced ad blocker for iOS
At a glance
- What is it?
- AdGuardForiOS is a GPL-3.0 Swift app that blocks ads in Safari through a content blocker extension and, in paid builds, resolves DNS itself with DoH, DoT or DNSCrypt. The blocking half is standard; the DNS half is what makes it unusual, and it is also where the design trade-offs sit.
- Who is it for?
- Adopt AdGuard for iOS if you want Safari ad blocking with a DNS layer you control, and you are willing to accept that the encrypted DNS and custom subscription features live behind the Premium tier while the source is GPL-3.0. Do not adopt it if you need per-app VPN filtering for the whole device or a managed configuration path, since the README documents no such mechanism.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two blocking layers, and only one of them is open in every build
AdGuard for iOS is described in the repository as a Safari content blocker and privacy keeper for iOS. Mechanically it runs two distinct things. The first is a Safari content blocker, which is the iOS system extension that consumes a rule list and blocks requests before the page loads. The second is a local DNS resolver: the app describes configurable DNS settings and encrypted DNS support through DOH, DOT and DNSCrypt. Those two layers fail differently, and understanding that split explains most of the app's behaviour.
The DNS side is listed by the README as a Premium feature, alongside custom ad blocking subscriptions. The free build is the content blocker. That matters for anyone evaluating this as a self-hosted or auditable component: the rule engine that matters most for privacy, the part that decides which hostnames your device even asks about, is not in the version you get by default.
The repository is a real Xcode project rather than a single source folder. Top-level entries include AdGuardSDK/, AdguardExtension/, AdguardSafariExtension-iOS.xcworkspace/, Builder/, Libs/, SupportingScripts/ and fastlane/. The SDK sits alongside the app extensions, which is the layout you would expect from a project that has to keep a shared filtering core in sync between the host app and the extension that runs out of process.
Building it: nvm, brew ruby, and a Fastlane that needs your App Store key
The README's build instructions are aimed at someone reproducing an App Store build, not at someone who wants a sideloaded binary. Node 22 is required for the web extension, and the repository tells you to install it through nvm. Ruby comes from Homebrew, and bundler is pinned to a specific version, which is the kind of pin that tells you this process has bitten people before.
Start with Node and yarn, then point your shell at the Homebrew Ruby:
nvm install 22
nvm alias default 22
npm install -g yarnThen install Ruby and prepend it to your path in `~/.zshrc`:
brew install rubyThen add the Homebrew Ruby to your `~/.zshrc`:
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrcBundler is installed at a fixed version and configured to keep dependencies inside the checkout, so nothing lands in a shared gem path:
gem install bundler -v 2.3.9
bundle config set --local path '.bundle/vendor'
bundle installThe interesting constraint comes next. Before any Fastlane lane will run, an App Store Connect API key must be placed at `fastlane/AuthKey.p8`. Without that file, lanes fail regardless of how correct your Ruby setup is. Codesigning then needs the certificate provisioning step, and the README is explicit that by default these commands produce credentials for AdGuard and not for AdGuard Pro; the Pro variant requires `--env pro`.
Fastlane lanes, and which ones you are not supposed to run locally
The lane surface is small and each lane does one thing. Registering a device for testing uses the lane runner directly rather than a named lane:
bundle exec fastlane run register_device udid:"UUID" name:"Device name"Certificate provisioning is a separate step from building, which is the split that trips people up: `bundle exec fastlane prepare` gives you the signing certificates, and only then does `bundle exec fastlane tests` or `bundle exec fastlane build` produce something. The build lane targets the App Store version; the CI-only lanes produce an ad hoc build for inner testing, bump the build number and commit that bump to git, and upload the previously built version to TestFlight. Ad hoc, increment and testflight_beta are labelled CI-only in the README, so running them from a laptop is off the intended path.
Expired certificates have their own recovery route, and it is destructive:
bundle exec fastlane match nuke development
bundle exec fastlane match nuke distribution
bundle exec fastlane generate`match nuke` revokes the existing certificates rather than renewing them. The README describes these commands as the way to remove old certificates after expiration and generate them again.
For CI, the project uses spaceauth, and the README says to generate `FASTLANE_SESSION` with `bundle exec fastlane auth` and use it on the CI server.
Issue reporting splits filter bugs away from crash bugs
The contribution section draws a distinction that explains a lot about how the project handles blocking quality. Ordinary bugs and feature requests go to the GitHub issue tracker. Filter-related issues, meaning missed ads and false positives, go to a separate reporting tool at reports.adguard.com rather than to GitHub.
The reason is structural. Missed ads and false positives are filter-related issues, so the README directs users to a separate reporting tool rather than GitHub. Filing those as GitHub issues would flood the tracker with something the maintainers route elsewhere anyway. If you are evaluating this project, the filter reporting tool is also where you would file the evidence that blocking quality has regressed, and it is worth checking whether reports there get addressed before assuming a subscription is reliable.
Translations are handled as a separate track with their own documentation, and beta access runs through an application form rather than a public TestFlight link. Release planning lives on the repository's GitHub milestones page, which the README points to for approximate dates on upcoming iOS releases. The README itself does not contain a roadmap, so milestone visibility is the only scheduling signal the project publishes.
Maintenance state and what the last push actually tells you
The repository is not archived, and the last push was on 2026-09-28. Releases track the same cadence: v4.5.23.1054 on 2026-09-28, v4.5.22.1053 on 2026-08-26, and v4.5.21.1052 on 2026-07-01. The default branch is version/v4.5, so the codebase you clone is a maintenance branch for a specific major version rather than a trunk that will become 4.6.
That branch choice has a practical consequence. Anyone who vendors AdGuardSDK/ or the extensions directly has to decide whether to follow version/v4.5 or cut over at the next major. The repository does not document a deprecation schedule for 4.4 or earlier, so the upgrade cost for an integrator is something you would have to establish from the release notes yourself.
Licensing is GPL-3.0. That matters most for the SDK and the extension code, not for end users of the App Store build, but it does constrain anyone who wants to link the filtering core into a closed-source product.
Where this is the wrong tool
The scope is Safari on iOS. Requests from every other app on the device are not what this content blocker sees, and the README does not describe a device-wide VPN-based filtering path or a managed configuration profile for fleet deployment. If your requirement is per-app filtering across the whole device, or centrally configured settings pushed to managed iPhones, this project as documented does not meet it.
There is a second, quieter limit. Because the encrypted DNS options and custom subscriptions are Premium features, the open repository is not the whole product. Reading the source tells you how filtering is implemented, but it does not tell you what the paid configuration surface exposes. Teams that need to audit exactly what their users can change should verify the Premium feature list before treating the open tree as a complete description of behaviour.
Third, the build path is not a packaging path. Producing a working build requires an App Store Connect API key, a codesigning identity, and the TestFlight upload flow. There is no documented command in the README that produces a sideloadable artifact for arbitrary devices, which makes this a poor fit for a CI pipeline that just wants an .ipa.
How this differs from a plain filter list subscription
A bare filter list, downloaded and handed to a content blocker extension, only sees requests the browser is making and can only block by URL pattern. AdGuard's approach adds a layer that can change what the device resolves in the first place, so a blocked hostname can be answered locally instead of being requested and then refused. The choice of DoH, DoT and DNSCrypt is the practical difference: it moves trust from the network resolver to a provider you name, and the app is the thing that makes that choice configurable.
The alternative worth comparing against is a standalone DNS filtering app that does only the resolver half, with no Safari rule engine. Such a tool generally filters by domain, while a content blocker can match request-level patterns inside a page. The reverse alternative, keeping a content blocker and leaving DNS to the carrier, is simpler and leaks less configuration surface, at the cost of no encrypted resolver. AdGuard sits on both sides of that line, which is its appeal and also why its configuration is more complex than either single-purpose tool.
Editorial conclusion
Adopt AdGuard for iOS if you want Safari ad blocking with a DNS layer you control, and you are willing to accept that the encrypted DNS and custom subscription features live behind the Premium tier while the source is GPL-3.0. Do not adopt it if you need per-app VPN filtering for the whole device or a managed configuration path, since the README documents no such mechanism. Before committing, verify that your DNS provider actually supports the protocol you select, and check the project's GitHub milestones page, because the README routes release timing there rather than describing it.
Frequently asked questions
Can I build AdGuard for iOS from source without an Apple Developer account?
No. The documented build path places an App Store Connect API key at `fastlane/AuthKey.p8` before any Fastlane lane runs, and the release process uses TestFlight upload. The README gives no command that produces a sideloadable build for an arbitrary device.
Which encrypted DNS protocols does AdGuard for iOS support?
The README names DOH, DOT and DNSCrypt as part of the configurable, encrypted DNS support offered in the Premium features. Basic ad blocking in Safari is the non-premium part of the app.
Where should I report a missed ad or a false positive in AdGuard for iOS?
Filter-related issues go to the project's own reporting tool at reports.adguard.com, not to the GitHub issue tracker. General bugs and feature requests use the New issue button on the repository's issues page.
How do I run the AdGuard for iOS tests?
Run `bundle exec fastlane tests` through bundler. That requires the Ruby and bundler setup in the README first, plus signing certificates produced by `bundle exec fastlane prepare`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/adguardteam-adguardforios)Was this useful?