Validate AEM Dispatcher Config Like Cloud Manager
Most AEM projects keep a root-level dispatcher/
folder (as generated by the AEM Project Archetype). If your repo doesn’t have it yet, create one using the archetype or copy a starter from your team, and place it alongside your top-level pom.xml
.
Then download the AEM as a Cloud Service SDK from Adobe Software Distribution to get the Dispatcher Tools (validate.sh
, docker_run.sh
). For a step-by-step SDK setup, see: https://www.shiftsaas.com/adobe-experience-manager/setting-up-aem-sdk-local-environment/
Set Up Local Dispatcher Validation with the AEM SDK
Download the latest AEM SDK from Adobe’s Software Distribution portal to mirror Cloud Service locally and validate configurations with the most recent release.

Once extracted, the SDK provides both a .sh
script for macOS/Linux and a .zip
package for Windows. On macOS/Linux, you can make the script executable and run it, for example:
chmod +x aem-sdk-dispatcher-tools-2.0.256-unix.sh
./aem-sdk-dispatcher-tools-2.0.256-unix.sh
On Windows, simply use the .zip
package—no extra commands are required.

The extracted dispatcher SDK includes bin/
, docs/
, lib/
, and src/
folders—providing scripts, documentation, and dispatcher images needed to validate configs and simulate Cloud Manager checks locally.
dispatcher-sdk-2.0.256 % tree
.
├── README
├── bin
│ ├── docker_immutability_check.sh
│ ├── docker_run.sh
│ ├── docker_run_hot_reload.sh
│ ├── update_maven.sh
│ ├── validate.sh
│ ├── validator -> ./validator-darwin-arm64
│ ├── validator-darwin-arm64
│ └── validator-linux-arm64
├── docs
│ ├── README.html
│ ├── TransitionFromAMS.html
│ ├── TroubleShooting.html
│ └── Validator.html
├── lib
│ ├── configuration_reloading.sh
│ ├── dispatcher-publish-amd64.tar.gz
│ ├── dispatcher-publish-arm64.tar.gz
│ ├── dummy_gitinit_metadata.sh
│ ├── httpd-reload-monitor
│ ├── httpd-vhosts
│ ├── immutability_check.sh
│ ├── import_sdk_config.sh
│ └── overwrite_cache_invalidation.sh
└── src
Next, copy bin
, docs
, and lib
into the repo’s dispatcher
folder—your project tree will then look like this.

With the SDK bits in place, run the validator from the dispatcher module itself to mirror Cloud Manager’s checks. This command executes three passes—static rules, a dockerized Apache/dispatcher syntax check, and immutable-file parity.
cd dispatcher
./bin/validate.sh src
Use the AEM SDK’s dispatcher tools to check your config locally: run bin/validate.sh src
to compile, then bin/docker_run.sh
to test routing, filters, and caching. Next, we walk through Phases 1–3—the typical failures in each and how to fix them.
Validator Phases 1-3: Top Failures & How to Fix Them Quickly
Before you run the dispatcher validator, choose and configure the correct host pattern—use dedicated, host-specific vhosts for each environment and keep a strict 404 catch-all—so validation and runtime tests reflect the intended routing, headers, and caching.
- Catch-all vhost:
ServerAlias *
indefault.vhost
. Matches anyHost
header. Use only as a strict 404 fallback for unknown hosts. - Dedicated vhost (recommended):
ServerName portaldev.flagtick.com
with optionalServerAlias portaldev.flagtick.local
. Create one per environment/domain for predictable routing, headers, and cache rules.
For example, with option 1 we assume that flagtick.com points to another server, while portaldev.flagtick.com targets AEMaaCS. In this case, we would use flagtick.vhost (the name flagtick can be changed to custom.vhost or any other name when using option 1).
# Include customer defined variables
Include conf.d/variables/custom.vars
<VirtualHost *:80>
ServerName "publish"
# Put names of which domains are used for your published site/content here
ServerAlias "*"
# Use a document root that matches the one in conf.dispatcher.d/default.farm
DocumentRoot "${DOCROOT}"
# URI dereferencing algorithm is applied at Sling's level, do not decode parameters here
AllowEncodedSlashes NoDecode
# Add header breadcrumbs for help in troubleshooting
<IfModule mod_headers.c>
Header add X-Vhost "publish"
</IfModule>
<Directory />
<IfModule disp_apache2.c>
# Some items cache with the wrong mime type
# Use this option to use the name to auto-detect mime types when cached improperly
ModMimeUsePathInfo On
# Use this option to avoid cache poisioning
# Sling will return /content/image.jpg as well as /content/image.jpg/ but apache can't search /content/image.jpg/ as a file
# Apache will treat that like a directory. This assures the last slash is never stored in cache
DirectorySlash Off
# Enable the dispatcher file handler for apache to fetch files from AEM
SetHandler dispatcher-handler
</IfModule>
Options FollowSymLinks
AllowOverride None
# Insert filter
SetOutputFilter DEFLATE
# Don't compress images
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
# Prevent clickjacking
Header always append X-Frame-Options SAMEORIGIN
</Directory>
<Directory "${DOCROOT}">
AllowOverride None
Require all granted
</Directory>
<IfModule disp_apache2.c>
# Enabled to allow rewrites to take affect and not be ignored by the dispatcher module
DispatcherUseProcessedURL On
# Default setting to allow all errors to come from the aem instance
DispatcherPassError 0
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine on
Include conf.d/rewrites/rewrite.rules
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{REQUEST_URI} !^/dispatcher/invalidate.cache
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Rewrite index page internally, pass through (PT)
RewriteRule "^(/?)$" "/index.html" [PT]
</IfModule>
</VirtualHost>
After adding flagtick.vhost
do these steps in order:
# 1. Enable the vhost (if not already)
ln -s ../available_vhosts/flagtick.vhost src/conf.d/enabled_vhosts/flagtick.vhost
# 2. Run validator
./bin/validate.sh src
On the other hand, if you follow option 2, the ServerName
and ServerAlias
should point to portaldev.flagtick.com in the Cloud Manager domain mapping, with portaldev.flagtick.local as an optional local alias, defined inside portal_dev_publish.vhost.
## FlagTick DEV Publish virtual host
<VirtualHost *:80>
AllowEncodedSlashes On
## Primary hostname for FlagTick DEV publish
ServerName portaldev.flagtick.com
ServerAlias portaldev.flagtick.local
ServerAlias flagtick.cloud
## Dispatcher document root
DocumentRoot ${DOCROOT}
<IfModule mod_headers.c>
Header always add X-Vhost "flagtick-publish-dev"
Header merge X-Frame-Options SAMEORIGIN "expr=%{resp:X-Frame-Options}!='SAMEORIGIN'"
Header merge X-Content-Type-Options nosniff "expr=%{resp:X-Content-Type-Options}!='nosniff'"
Header set Permissions-Policy "browsing-topics=()"
</IfModule>
<Directory />
<IfModule disp_apache2.c>
ModMimeUsePathInfo On
DirectorySlash Off
SetHandler dispatcher-handler
</IfModule>
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory "${DOCROOT}">
AllowOverride None
Require all granted
</Directory>
<IfModule disp_apache2.c>
DispatcherUseProcessedURL 1
DispatcherPassError 0
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine on
## Enforce HTTPS
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{REQUEST_URI} !^/dispatcher/invalidate.cache
RewriteRule (.*) https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
## Project-specific rewrites
Include conf.d/rewrites/flagtick_301_rewrite.rules
Include conf.d/rewrites/portal_dev_rewrite.rules
Include conf.d/rewrites/rewrite.rules
</IfModule>
</VirtualHost>
Next, enable your custom vhost and farm by creating symlinks from available_*
into enabled_*
. This ensures Apache picks up the vhost and Dispatcher routes requests through the correct farm.
ln -s ../available_vhosts/portal_dev_publish.vhost
src/conf.d/enabled_vhosts/portal_dev_publish.vhost
ln -s ../available_farms/portal_dev_publish.farm src/conf.dispatcher.d/enabled_farms/portal_dev_publish.farm
In /etc/hosts
, make sure you have:
127.0.0.1 portaldev.flagtick.local
Then:
- Local Author (4502):
http://portaldev.flagtick.local:4502/
- Local Publish (4503):
http://portaldev.flagtick.local:4503/
- Dispatcher SDK (8080/8081):
http://portaldev.flagtick.local:8081/
Below folder structure keeps the configuration organized and makes validation easier. Each environment’s vhost and farm can be tested separately with the Dispatcher SDK or Docker before deploying to Adobe Cloud Manager.


One of the most common Phase 1 screw-ups is when the validator says it can’t find any .vhost
files in conf.d/enabled_vhosts/
.
./bin/validate.sh src
Phase 1: Dispatcher validator
Error: no .vhost files found in conf.d/enabled_vhosts/
Warning: ignoreUrlParameters not set in farm (marketing params recommended)
Phase 1 failed
That usually means you copied the vhost files instead of linking them, or the symlinks got wiped. Cloud Manager only cares about symlinks in enabled_vhosts/
, so the fix is easy: keep the real files in available_vhosts/
and drop links back in with ln -s
.
ln -s ../available_vhosts/default.vhost src/conf.d/enabled_vhosts/default.vhost
After fixing the symlinks, it is a good idea to double-check that Apache can actually parse your configs before re-running the full validator. The simplest way is by running the built-in syntax check:
httpd -t
On macOS, the main config file is typically located at /etc/apache2/httpd.conf
. If you see the warning about “Could not reliably determine the server’s fully qualified domain name”, you can fix it by editing that file and setting:
ServerName localhost
Validate routing, caching, and filter rules in the same runtime as Cloud Manager. Install and run Docker locally to avoid errors:

Next, move into the dispatcher lib directory, locate the file (e.g., dispatcher-publish-arm64.tar.gz
), and load it into Docker Desktop:
cd ../dispatcher/lib
docker load -i dispatcher-publish-arm64.tar.gz
Once loaded, open Docker Desktop and confirm the Dispatcher Adobe image is available by running:
% docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
adobe/aem-cs/dispatcher-publish 2.0.256 86f18dd3966f 6 weeks ago 149MB
With the image confirmed, you can now start the dispatcher container. First, prepare an out
folder with your config, then launch using the provided script:
cd dispatcher
mkdir out
cp -R src/* out/
./bin/docker_run.sh out host.docker.internal:4503 8080
Note: This mounts your dispatcher configuration into the container and connects it to your local AEM Publish instance running on port 4503
.
There is common issue which that Dispatcher starts but can’t reach AEM Publish on port 4503, so the script keeps retrying with the ‘Sleeping for 5s…’ message.
%./bin/docker_run.sh ../out host.docker.internal:4503 8080
Darwin MacBook-Pro-cua-Vuong.local 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T8132 arm64
/bin/zsh
Running script /docker_entrypoint.d/05-display-image-version.sh
...
...
Waiting until port 4503 on host.docker.internal is available (with timeout of 1s)
Sleeping for 5s to wait until port 4503 on host.docker.internal is available
Sleeping for 5s to wait until port 4503 on host.docker.internal is available
If 8080 is in use, run docker ps
, stop the blocker with docker kill <id>
(or docker rm -f <id>
), or start Dispatcher on a different port.
Which diagram below shows requests hitting Dispatcher (8080); cache misses proxy to Publish (4503). Author (4502) activates content to Publish and triggers cache invalidation back to Dispatcher.

Before moving further, when we add the SDK folders from AEMaaCS such as lib, docs, and bin into our local setup for validation, we should also update our .gitignore. This ensures these large SDK artifacts are not accidentally committed and pushed to AEMaaCS Cloud.
# dispatcher-sdk
dispatcher/lib
dispatcher/docs
dispatcher/bin
dispatcher/cache
dispatcher/out
Validate Using WKND Prior to Your Project
With Dispatcher running, the next step is to add real content — Adobe’s WKND site provides a complete AEM structure out of the box.

Note: If you are running Java 11 locally, use the WKND 3.2.0 release (for example, aem-guides-wknd.all-3.2.0.zip). For Java 21, you can install the latest WKND from Adobe’s GitHub: aem-guides-wknd
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for WKND Sites Project - Reactor Project 3.2.0:
[INFO]
[INFO] WKND Sites Project - Reactor Project ............... SUCCESS [ 0.726 s]
[INFO] WKND Sites Project - Core .......................... SUCCESS [ 38.908 s]
[INFO] WKND Sites Project - UI Frontend ................... SUCCESS [ 38.375 s]
[INFO] WKND Sites Project - UI apps structure ............. SUCCESS [ 1.040 s]
[INFO] WKND Sites Project - UI apps ....................... SUCCESS [ 4.041 s]
[INFO] WKND Sites Project - UI content .................... SUCCESS [ 1.411 s]
[INFO] WKND Sites Project - UI config ..................... SUCCESS [ 0.162 s]
[INFO] WKND Sites Project - UI sample content ............. SUCCESS [ 0.816 s]
[INFO] WKND Sites Project - All ........................... SUCCESS [ 42.472 s]
[INFO] WKND Sites Project - Integration Tests ............. SUCCESS [ 26.077 s]
[INFO] WKND Sites Project - Dispatcher .................... SUCCESS [ 0.564 s]
[INFO] WKND Sites Project - UI Tests ...................... SUCCESS [ 0.347 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:47 min
[INFO] Finished at: 2025-09-27T21:19:01+07:00
Next, copy the Dispatcher configuration from your custom project into the WKND setup, then update rewrite.rules
to point the default page to portaldev.flagtick.local
.
On AEM Publish (4503), WKND installs a root mapping through RootMappingServlet.cfg.json
so that /
resolves directly to /content/wknd/us/en.html
. However, Dispatcher (8080) does not use Sling mappings.
// RootMappingServlet.cfg.json
{
"rootmapping.target": "/content/wknd/us/en.html"
}
// rewrite.rules
# rewrite for root redirect
RewriteRule ^/?$ /content/${CONTENT_FOLDER_NAME}/us/en.html [PT,L]
For redirecting to login.html
, the configuration should be set up like this
// RootMappingServlet.cfg.json
{
"rootmapping.target": "/content/<project>/us/login.html"
}
// rewrite.rules
# Map the root folder to the login page
RewriteRule ^/?$ /content/<project>/us/login.html [PT,L]
That is why we copy our project’s Dispatcher configuration into WKND and update rewrite.rules
— ensuring that hitting http://portaldev.flagtick.local:8080/
also goes to the intended homepage.
It is important to understand where different types of redirects should live. AEM itself is responsible for content-driven routing (like site root or language mapping), while Dispatcher is better suited for infrastructure-level redirects (like forcing HTTPS or handling domain aliases).
If redirect is business/content logic → do it in AEM
/etc/map.publish
- Sling Resource Resolver
- Redirect Map Manager (ACS Commons, etc.)
If redirect is infra/domain routing → do it in Dispatcher
- e.g., force HTTPS, remove
www
, vanity domain to homepage
Once your Dispatcher is up on 8080, you can test a few common checks to confirm it’s wired correctly with AEM Publish (4503).
First, hit the Dispatcher root — a 200 OK
confirms it’s proxying to Publish.
curl -v http://portaldev.flagtick.local:8080/
* Host portaldev.flagtick.local:8080 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.1, 127.0.0.1
* Trying 127.0.0.1:8080...
* Connected to portaldev.flagtick.local (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: portaldev.flagtick.local:8080
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Sun, 28 Sep 2025 03:34:06 GMT
< Server: Apache
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< Last-Modified: Sun, 28 Sep 2025 01:58:38 GMT
< ETag: "1dd2d-63fd2db7ebc91"
< Accept-Ranges: bytes
< Content-Length: 122157
< Cache-Control: max-age=300
< Expires: Sun, 28 Sep 2025 03:39:06 GMT
< Vary: Accept-Encoding
< X-Vhost: publish
< Content-Type: text/html;charset=utf-8
<
<!DOCTYPE HTML>
Add a header to show whether requests are served from cache (HIT, MISS, REFRESH).
<IfModule mod_headers.c>
Header always add X-Vhost "flagtick-publish-dev"
Header merge X-Frame-Options SAMEORIGIN "expr=%{resp:X-Frame-Options}!='SAMEORIGIN'"
Header merge X-Content-Type-Options nosniff "expr=%{resp:X-Content-Type-Options}!='nosniff'"
Header set Permissions-Policy "browsing-topics=()"
# Show Dispatcher cache state (HIT, MISS, REFRESH)
Header add X-Cache-Status "%{DISP_CACHE_STATE}e"
</IfModule>
If you want to check cache HIT or MISS in HTTP headers, note that the Local Dispatcher SDK doesn’t add this header by default — you have to configure it in Apache. Otherwise, the easiest way is to tail the log file with:
docker ps
docker exec -it <container_id> ls -l /var/log/apache2
docker exec -it <container_id> tail -f /var/log/apache2/dispatcher.log
curl -I http://portaldev.flagtick.local:8080/content/wknd/us/en.html
If the header isn’t available, you can always confirm caching status in the dispatcher log, which reports whether a request was served from cache (hit
), fetched fresh (miss
), or revalidated (refresh
).
[28/Sep/2025:06:45:09 +0000] "HEAD /content/wknd/us/en.html HTTP/1.1" - hit [publishfarm/-] 8ms "portaldev.flagtick.local:8080"
Running the Local Dispatcher over HTTPS
Now that caching is verified, the next step is to enable HTTPS support for your local Dispatcher. This requires configuring SSL certificates in AEM’s Security tools.

Open Tools → Security → SSL Configuration in AEM. Here you will create a Key Store and Trust Store by setting passwords, as shown in the screenshot. These secure the certificates AEM uses to serve content over HTTPS.

In AEM’s SSL wizard:
- Key Store password → you set it yourself. It protects the private keys and certificates inside the keystore.
- Trust Store password → you also set it yourself. It secures the trusted CA certificates AEM will use.
Reference link: https://experienceleague.adobe.com/en/docs/experience-manager-learn/foundation/security/use-the-ssl-wizard
At the Key & Certificate step, upload your private key and certificate to finish SSL setup. For local use, Adobe offers a ZIP with a self-signed key (.DER) and cert (.CRT), valid until July 2028 with passphrase admin
. As shown, just select the files to proceed.

Next, bind the certificate by setting your local hostname (e.g., portaldev.flagtick.local
) and an HTTPS port like 8443
in the SSL Connector screen. This enables AEM to serve content securely over HTTPS.

⚠️ Just make sure:
- You have mapped
portaldev.flagtick.local
in/etc/hosts
. - Your Dispatcher vhost is updated to listen on
*:8443
(or proxied accordingly).
In your case (from earlier screenshots), you were doing the setup in Author’s Tools → Security → SSL Configurations, so https://portaldev.flagtick.local:8443/
will point to Author.
Wrapping up
You now have a Dispatcher configuration validated just like Cloud Manager. You built and deployed it, ran the container locally, checked logs (dispatcher.log
), and confirmed cache/invalidation against Publish. Key takeaways: validate rules in Docker before pushing, always watch for HIT/MISS in headers, and keep vhost/filter rules as lean as possible.
Side note: Cloud Manager validation is stricter than local SDK defaults. Always sync your local dispatcher SDK version with the Cloud Manager version to avoid surprises.
What’s next
In the next part, we’ll move from the back-end Dispatcher to the front-end workflow in AEM. You’ll:
- Break down the ui.frontend module and its folder/package.json structure.
- Refine Webpack configs (
webpack.common.js
,webpack.plugins.js
,main.ts
) for cleaner builds. - Use tools like webpack-bundle-analyzer, ESLint, and Stylelint for optimization and code quality.
- Automate clientlibs generation with
npm run clientlibs
, integrating output intoui.apps
. - Apply best practices for a lightweight and efficient AEM front-end development workflow.
Prereqs for the next article
- Docker Desktop installed.
- AEM Publish running locally (e.g.,
http://localhost:4503
). - The Adobe Dispatcher Tools/SDK or AMS Dispatcher Docker image available.
- A hosts entry for your test domain (e.g.,
127.0.0.1 flagtick.local
).
When you are ready, jump to: “Validate AEM Dispatcher Config Like Cloud Manager”.