Firewalls, security plugins & server compatibility
Fix the common ways security plugins, WAFs, caching, and server settings block Vakanssi: REST API blockers, wp-admin lockdowns, upload firewalls, Nginx file protection, cron, and email.
On this page(10)
Vakanssi runs two public-facing flows (the application form and, on Pro, the interview booking page) plus a REST-backed admin UI. Security plugins, WAFs, and server hardening sometimes block these legitimate paths. Every issue on this page is detectable under Hiring, then Tools, then Status, with Run compatibility test, and most appear in Tools, then Site Health too.
The test reports one row per path: Application form (public POST), REST API (admin boards), Open positions and job pages, and Anti-spam challenge, plus Interview booking page on Pro Pro feature, requires a Vakanssi Pro license. Since v0.36.3 the anti-spam row says whether your challenge has been tested with its current keys, and the form row no longer claims “working” when a challenge is on, since the test cannot solve one.
1. REST API blockers (admin boards fail to load)
The candidates board, notes, interviews, and scorecards call the WordPress
REST API under the vakanssi/v1 namespace. Plugins that disable the REST API
break these screens: Vakanssi shows a red notice on the board when this
happens.
- WP Cerber: under WP Cerber, then Dashboard, then Hardening,
with Disable REST API on, add
vakanssi(only the part before the first slash, notvakanssi/v1) to Allow these REST API namespaces, or tick Allow logged-in users. Administrators are allowed by default, so test the fix while signed in as a Recruiter. - Kadence Security (formerly Solid Security): no change needed. Its
Restricted Access option (Security, then Settings, then
Advanced, then WordPress Tweaks, then REST API) only restricts
core
wp/v2routes, notvakanssi/v1. - “Disable REST API” plugin and similar: allow requests for logged-in
users, or whitelist the
vakanssi/v1namespace.
Staff use the REST API while logged into wp-admin; it is never exposed to candidates by Vakanssi (all routes require capabilities).
2. Web application firewalls (applications get blocked)
An application submission is a multipart POST containing a PDF/Word CV and a free-text cover letter, exactly the shape generic WAF rules false-positive on. Symptom: candidates hit a 403/“blocked” page on submit; the site owner sees nothing.
-
Wordfence: if submissions are blocked, check Wordfence, then Tools, then Live Traffic for blocks on
admin-post.phpand use Add Param to Firewall Allowlist on the blocked request, or set Web Application Firewall Status to Learning Mode (Firewall, then All Firewall Options) while you submit a test application. -
ModSecurity / OWASP CRS: the usual false positives are rules 941100 (XSS heuristic) and 942360 (SQLi heuristic) firing on cover letters. Scope-disable them for the form endpoint only:
<LocationMatch "/wp-admin/admin-post\.php"> SecRuleRemoveById 941100 942360 </LocationMatch> -
6G/7G/8G firewalls: if a submission 403s, comment out the one rule line that matched rather than disabling the whole firewall. These rulesets have no allowlist setting, and they check the URL, query string, user agent, and cookies, not the uploaded CV.
3. wp-admin lockdowns (the apply form breaks for everyone)
The application form posts to /wp-admin/admin-post.php, a core WordPress
endpoint that is designed to be publicly reachable. If you password-protect,
IP-restrict, or Cloudflare-Access-gate /wp-admin/*, candidates can no
longer apply. Exempt admin-post.php, which the form posts to, and
admin-ajax.php, which other plugins’ front-end features use:
# Apache example: basic-auth on wp-admin with exemptions
<FilesMatch "^(admin-post\.php|admin-ajax\.php)$">
Require all granted
</FilesMatch>
# Nginx example
location ~ ^/wp-admin/(admin-post|admin-ajax)\.php$ {
auth_basic off;
allow all;
# ... same fastcgi config as your other PHP location ...
}
Place the Nginx block before your other PHP location. If wp-admin is locked
with location ^~ /wp-admin/, Nginx never reaches this block; use
location /wp-admin/ (without ^~) for the lockdown instead.
On Cloudflare Access, add a separate application for each of
/wp-admin/admin-post.php and /wp-admin/admin-ajax.php with a Bypass
policy. Zone Lockdown has no bypass: give those two URLs their own
higher-priority lockdown rule that allows every IP, or move the lockdown to a
custom rule that excludes them.
4. Page caching & CDNs
Two rules keep caching plugins (WP Rocket, LiteSpeed Cache, W3TC) and CDN-level HTML caching compatible:
- Expire cached job pages within two days. Since v0.4.0, anonymous applications fall back from a stale cached nonce to a signed time-trap token, valid for 2 days. A page cached longer carries an expired form and the candidate loses what they typed, so expire pages within two days. Since v0.33.36, Site Health flags this (“Applications are being rejected because the form had expired”).
- Never cache booking URLs (Scheduling Pro feature, requires a Vakanssi Pro license). Interview booking links use clean paths
like
https://example.com/vakanssi-booking/<token>, withhttps://example.com/?vakanssi_book=<token>as the fallback (on plain permalinks, for example). If your cache or CDN ignores query strings for HTML, or caches the/vakanssi-booking/path itself, the booking page can silently become the homepage. Exclude the/vakanssi-booking/path and requests with avakanssi_bookquery parameter from caching. (Vakanssi already sends no-cache headers on that page.)
Check your caching plugin’s default lifespan against the two-day window. LiteSpeed Cache’s Default Public Cache TTL is 604800 seconds (7 days), so lower it to 172800 or less. WP Rocket’s default (10 hours) and W3 Total Cache’s (3600 seconds) are already inside it.
5. Cloudflare
-
Real visitor IPs: Vakanssi rate-limits applications per IP using
REMOTE_ADDR. Behind Cloudflare, restore real IPs at the server level (mod_remoteipwithCF-Connecting-IPon Apache,ngx_http_realip_moduleon Nginx) so throttles apply to visitors rather than Cloudflare’s edges. Or set Visitor IP source under Hiring, then Settings, then Application form, to Cloudflare or reverse proxy so Vakanssi reads the real IP even without server-level configuration. -
Only a trusted hop is believed. In Cloudflare mode, Vakanssi reads
CF-Connecting-IPonly when the request itself arrived from a published Cloudflare edge address; from anywhere else the header is ignored andREMOTE_ADDRis used instead. In Reverse proxy mode, the immediate peer is trusted by definition, that is what choosing the mode means, and the visitor address is the right-mostX-Forwarded-Forentry that is not itself a trusted proxy. -
A CDN in front of a load balancer (or any second hop beyond the proxy Vakanssi already trusts by mode) needs that extra address declared, or every visitor behind it shares one rate-limit bucket. Add it in
wp-config.php:define( 'VAKANSSI_TRUSTED_PROXIES', array( '10.0.0.0/8', '192.0.2.7' ) );Site Health reports both ways this can go quiet on its own: “Forwarded visitor IPs are being ignored” means a forwarded header arrived from a peer Vakanssi does not trust, and “More than one proxy is forwarding to this site” means a chain arrived but only its closest hop could be verified. Both point at the same fix, declaring the missing proxy address.
-
Bot Fight Mode challenges
wp-cron.phpand other loopback requests, which can stall scheduled tasks (see §7). WAF custom rules cannot skip Bot Fight Mode; add an IP Access rule that allows your own server’s IP, or use a real system cron. -
Caching: see §4, add a cache bypass rule for
vakanssi_book.
6. CV file storage on Nginx and managed hosts
Since v0.37.10, Vakanssi stores CVs in a protected folder inside wp-content/uploads
(vakanssi, with a deny rule, index guards, and random file names) and verifies
over HTTP that the folder is not publicly readable. WordPress.org does not allow a plugin
to write files outside the WordPress install, so this is the default for every site; the
VAKANSSI_STORAGE_DIR constant still moves storage outside the web root if you want that.
Two different Site Health messages point at this folder, and they mean different things. Since v0.32.8, “Vakanssi cannot store candidate files” is the more serious one: the folder itself cannot be created or written, so every application with a CV attachment is failing.
Check that your web server’s
user can write to the path Site Health names, and, if wp-config.php
defines VAKANSSI_STORAGE_DIR, that the path it points to (or its parent)
exists and is writable. See
Troubleshooting.
If instead Site Health or the admin notice flags the folder’s lockdown, CVs are being stored but their direct-download protection is in question, in one of two ways. A critical “candidate files can be downloaded directly” message means the check ran and the folder answered a direct web request: treat this as a confirmed exposure.
A “could not confirm” warning is different: the check itself did not
complete. The site may be unable to make requests to its own URLs, uploads may
be served from another host (such as a CDN), or, for a custom
VAKANSSI_STORAGE_DIR folder only, the folder answered “not found” instead of
“forbidden”, which the check cannot tell apart from a wrong address.
That is not evidence of a leak, just an unanswered question. Either way, the fix is the same:
-
Nginx:
.htaccessfiles are ignored, so add the rule Vakanssi generates inwp-content/uploads/vakanssi/nginx.conf.txt:location ^~ /wp-content/uploads/vakanssi/ { deny all; return 403; }On multisite, the rule above covers the main site. Each other site has its own folder under
wp-content/uploads/sites/<id>/vakanssi/, and the admin notice on that site shows its own path. -
Any host: define a storage path outside the public web folder in
wp-config.php:define( 'VAKANSSI_STORAGE_DIR', '/home/example/private/vakanssi' ); -
Kinsta / Flywheel: ask support to add the deny rule above (custom Nginx rules go through their support teams).
-
Pantheon: custom Nginx rules aren’t supported; set
VAKANSSI_STORAGE_DIRto the platform-protected private path.../wp-content/uploads/private/vakanssi. Alternatively, list/wp-content/uploads/vakanssiunderprotected_web_pathsinpantheon.yml, which answers 403.
After adding the rule or moving storage, click Check storage again next to the storage row on Hiring, then Tools, then Status to re-run the check immediately. Vakanssi otherwise retries on its own, but only once a day after a successful check or every 15 minutes after one that could not confirm anything, so waiting it out can leave a fixed server showing a stale warning for a while.
7. Scheduled tasks (GDPR retention, job expiry, webhook retries)
Vakanssi relies on WP-Cron for its daily retention purge, the job that
deletes candidate data past your configured retention period. If WP-Cron
never runs (low traffic, DISABLE_WP_CRON without a system cron, or bot
protection challenging wp-cron.php), the purge silently stops, a
compliance problem, not just a technical one. Set up a real cron job; see
Going live: production setup. Site
Health warns when the purge hasn’t run for two days.
The support report on the Status tab, and the Vakanssi panel under Tools, then Site Health, then Info, list when each scheduled task last ran. On the free plugin that section is called Cron & maintenance; on Pro it is Integrations & cron and also counts webhook deliveries waiting for a retry.
8. Email delivery
Application confirmations and interview invites go through wp_mail(). Many
hosts disable PHP mail() or block SMTP ports, and unauthenticated mail
lands in spam. Use an SMTP/API mailer plugin (WP Mail SMTP or similar) with
SPF/DKIM configured. Vakanssi logs every send under the hood and Site Health
reports recent failures from the last 7 days.
Since v0.36.4, the healthy Site Health result reads “Vakanssi emails are being sent”, not “delivered”: it can see that your site handed each message to its mail server, not whether it reached an inbox. A site that has sent nothing yet is told so, instead of being reported as working. See Email delivery for the setup and for how to test it.
Also since v0.36.4, Site Health checks your anti-spam challenge (“Vakanssi anti-spam challenge is working”), so a challenge set up for the wrong web address, or never tested, shows up there instead of silently refusing every applicant.
9. Outbound HTTP restrictions
If wp-config.php sets WP_HTTP_BLOCK_EXTERNAL, add the hosts Vakanssi
needs to WP_ACCESSIBLE_HOSTS:
define( 'WP_ACCESSIBLE_HOSTS', implode( ',', array(
'example.com', // your own host: Vakanssi self-checks over loopback
'hooks.slack.com', // if you use Slack webhooks
'*.webhook.office.com', // if you use Microsoft Teams
'*.logic.azure.com', // Teams workflows
'challenges.cloudflare.com', // if you use Turnstile
'api.hcaptcha.com', // if you use hCaptcha
'www.google.com', // if you use reCAPTCHA
) ) );
Slack webhooks belong to Integrations Pro feature, requires a Vakanssi Pro license, and hCaptcha and reCAPTCHA to
Advanced anti-spam (Pro); Turnstile is free. Webhook receivers on
private/internal addresses (10.x, .local, .internal) are rejected by
design (SSRF protection).
10. Several sites on one MySQL server
Vakanssi uses MySQL named locks (GET_LOCK) so two requests never do the same
work at once: email resends and interview scheduling, and on Pro, the
stage-history backfill, webhook deliveries and candidate merges. Named locks
are shared by everything on the MySQL server, not scoped to one database.
Since v0.37.7, each lock name includes a hash of the database name and table
prefix. Two WordPress sites on the same server, even with the same wp_
prefix, no longer block each other’s locks. There is nothing to configure.