WordPress wp_safe_redirect Example: Prevent Open Redirects
Use wp_safe_redirect and wp_validate_redirect to prevent open redirects, validate return URLs, choose status codes, and exit safely after redirects.
Published
April 22, 2026
Reading Time
2 min read
Updated
April 22, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 22, 2026.
Full Report
Last reviewed: April 22, 2026
Redirect code looks harmless until a plugin accepts a user-controlled destination and creates an open redirect. Attackers use open redirects to make phishing links look trusted because the URL starts on a legitimate domain.
This guide shows how to use wp_safe_redirect() for local redirects, how to validate a return URL, and why redirect code should almost always call exit immediately after sending headers.
Problem this prevents
A redirect parameter such as ?redirect_to=https://attacker.example should not send visitors away from the site unless that behavior is explicitly intended and safely allowlisted. WordPress provides wp_safe_redirect() to restrict redirects to allowed hosts.
Safe admin redirect after save
<?php
function vulnwp_redirect_after_settings_save() {
$url = add_query_arg(
array(
'page' => 'vulnwp-settings',
'updated' => '1',
),
admin_url( 'options-general.php' )
);
wp_safe_redirect( $url );
exit;
}
The redirect target is built from trusted WordPress functions and local query arguments. The exit prevents later code from continuing after redirect headers are sent.
Validate a return URL
function vulnwp_get_safe_return_url() {
$fallback = admin_url( 'index.php' );
$raw = isset( $_GET['redirect_to'] )
? esc_url_raw( wp_unslash( $_GET['redirect_to'] ) )
: '';
if ( '' === $raw ) {
return $fallback;
}
return wp_validate_redirect( $raw, $fallback );
}
wp_validate_redirect() returns the provided URL only if it is safe. Otherwise it returns the fallback.
Use the safe URL
$return_url = vulnwp_get_safe_return_url();
if ( wp_safe_redirect( $return_url, 302, 'VulnWP Plugin' ) ) {
exit;
}
The third parameter sets the X-Redirect-By header so operators can identify which code initiated the redirect.
Status code choice
Use 302 for temporary redirects after form submissions, login flows, or admin actions. Use 301 only when the redirect is permanent and cache-safe. Browsers and intermediaries can cache permanent redirects aggressively, so a wrong 301 can be painful to undo.
Testing workflow
- Test a normal local return URL.
- Test an external attacker-style URL and confirm it falls back safely.
- Confirm the redirect happens before any output is sent.
- Confirm code exits after redirect.
- Check the
X-Redirect-Byheader during debugging.
When external redirects are legitimate
Some integrations need external redirects, such as OAuth, payment providers, or single sign-on systems. In those cases, use a strict allowlist of provider hosts and avoid accepting arbitrary destination URLs from request parameters.
$allowed_hosts = array( 'accounts.example.com', 'checkout.example.com' );
$host = wp_parse_url( $return_url, PHP_URL_HOST );
if ( ! in_array( $host, $allowed_hosts, true ) ) {
$return_url = home_url( '/' );
}
Production checklist
- Prefer
wp_safe_redirect()for local redirects. - Always call
exitafter successful redirects. - Use trusted fallbacks.
- Validate user-provided return URLs.
- Allowlist external hosts only when required.
- Do not redirect based on unsanitized request values.
Common mistakes
- Using
wp_redirect()with request data. That can create open redirects. - No fallback. Invalid return URLs need a safe destination.
- Forgetting
exit. Code can continue executing after headers are sent. - Allowlisting too broadly. Use exact host rules, not loose string matching.
- Redirecting after output. Headers must be sent before body output.
Related reading
For URL routing, read the rewrite rules example. For admin action protection, pair this with the nonce example.


