{"id":233266,"date":"2025-05-31T19:11:25","date_gmt":"2025-05-31T19:11:25","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/wps-protect-login-url-security-headers\/"},"modified":"2026-08-15T22:13:37","modified_gmt":"2026-08-15T22:13:37","slug":"wps-protect-login-url-security-headers","status":"publish","type":"plugin","link":"https:\/\/ko.wordpress.org\/plugins\/wps-protect-login-url-security-headers\/","author":20426440,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.0.0","stable_tag":"2.0.0","tested":"7.1","requires":"6.2","requires_php":"7.2","requires_plugins":null,"header_name":"WPS Protect: Login URL & Security Headers","header_author":"Muhammad Junaid Tariq","header_description":"Secure your site by hiding the login URL (protects from brute force attacks), adding security headers, and enforcing SSL.","assets_banners_color":"645213","last_updated":"2026-08-15 22:13:37","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/wordpress.org\/plugins\/wps-protect-login-url-security-headers\/","header_author_uri":"https:\/\/premiersol.co\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":522,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0":{"tag":"1.0","author":"junaid434","date":"2025-06-25 11:17:42"},"1.1":{"tag":"1.1","author":"junaid434","date":"2025-06-25 11:17:42"},"2.0.0":{"tag":"2.0.0","author":"junaid434","date":"2026-08-15 22:13:37"}},"upgrade_notice":{"2.0.0":"<p>Fixes a lockout that blocked administrators from wp-admin and broke all AJAX when a custom login URL was set, and drops a default CSP that broke inline scripts and styles. Adds brute-force protection and emergency login recovery. Settings migrate automatically. Needs PHP 7.2.<\/p>"},"ratings":[],"assets_icons":{"icon-256x256.png":{"filename":"icon-256x256.png","revision":3304050,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3312453,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3312453,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0","1.1","2.0.0"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"Login Protection \u2014 custom slug, block behaviour, emergency access and active lockouts.","2":"Security Headers \u2014 per-header toggles with validation and recommended values.","3":"SSL \u2014 HTTPS enforcement with proxy-aware detection."}},"plugin_section":[],"plugin_tags":[2439,25642,602,600,153786],"plugin_category":[38,54],"plugin_contributors":[243208],"plugin_business_model":[],"class_list":["post-233266","plugin","type-plugin","status-publish","hentry","plugin_tags-brute-force","plugin_tags-hide-login","plugin_tags-login","plugin_tags-security","plugin_tags-security-headers","plugin_category-authentication","plugin_category-security-and-spam-protection","plugin_contributors-junaid434","plugin_committers-junaid434"],"banners":{"banner":"https:\/\/ps.w.org\/wps-protect-login-url-security-headers\/assets\/banner-772x250.jpg?rev=3312453","banner_2x":"https:\/\/ps.w.org\/wps-protect-login-url-security-headers\/assets\/banner-1544x500.jpg?rev=3312453","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/wps-protect-login-url-security-headers\/assets\/icon-256x256.png?rev=3304050","icon_2x":"https:\/\/ps.w.org\/wps-protect-login-url-security-headers\/assets\/icon-256x256.png?rev=3304050","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p><strong>WPS Protect<\/strong> combines the three defences most WordPress sites install three separate plugins for \u2014 a hidden login URL, brute-force rate limiting, and HTTP security headers \u2014 into one lightweight plugin with no upsells and no performance overhead.<\/p>\n\n<h4>Login URL protection<\/h4>\n\n<ul>\n<li>Move <code>wp-login.php<\/code> to a slug of your choice.<\/li>\n<li>Choose what the old login URL returns: a 404, your home page, a custom URL, or a plain message.<\/li>\n<li>Slugs are validated before they are saved, against reserved WordPress paths, existing pages and post-type archives, so you cannot make your own content unreachable.<\/li>\n<li>Signed-out visitors to <code>\/wp-admin\/<\/code> are sent to your login page with their destination preserved, instead of hitting a dead end.<\/li>\n<li>AJAX, <code>admin-post.php<\/code> and REST endpoints stay reachable, so front-end forms, carts and the block editor keep working.<\/li>\n<li>Password-protected posts, logout links, and password-reset emails all continue to work.<\/li>\n<\/ul>\n\n<h4>Emergency access<\/h4>\n\n<p>Losing your login URL should never mean editing the database. Three independent recovery routes:<\/p>\n\n<ol>\n<li>A one-time rescue URL that works from any page on your site.<\/li>\n<li><code>define( 'WPSP_DISABLE_LOGIN_GATE', true );<\/code> in <code>wp-config.php<\/code>.<\/li>\n<li>WP-CLI: <code>wp wpsp login-url<\/code>, <code>wp wpsp set-slug<\/code>, <code>wp wpsp disable-gate<\/code>.<\/li>\n<\/ol>\n\n<p>The site administrator is emailed whenever the rescue URL is used.<\/p>\n\n<h4>Brute-force protection<\/h4>\n\n<ul>\n<li>Escalating lockouts \u2014 the second offence lasts longer than the first.<\/li>\n<li>Tracks attempts per IP address <strong>and<\/strong> per username, so a distributed attack on one account is still caught.<\/li>\n<li>Covers every way WordPress checks a password: the login form, XML-RPC, the REST API, Application Passwords, WooCommerce and BuddyPress.<\/li>\n<li><code>system.multicall<\/code> is disabled, closing the XML-RPC amplification trick that turns one request into hundreds of password guesses.<\/li>\n<li>Requests to your old login URL count as attacks \u2014 nothing legitimate knows that address any more.<\/li>\n<li>Proxy-aware: forwarding headers are trusted only from proxy ranges you configure, so nobody can spoof a header to dodge the limiter or get themselves allowlisted.<\/li>\n<li>Optional honeypot field, and generic error messages so the login and lost-password forms stop confirming which accounts exist.<\/li>\n<li>Locked-out requests return <code>429<\/code> with <code>Retry-After<\/code>, so Cloudflare, Fastly or fail2ban can shed the attack at the edge.<\/li>\n<\/ul>\n\n<h4>Security headers<\/h4>\n\n<ul>\n<li>X-Frame-Options, X-Content-Type-Options, Referrer-Policy, X-XSS-Protection, X-Permitted-Cross-Domain-Policies, Strict-Transport-Security, Content-Security-Policy (including Report-Only), Permissions-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy and CORS.<\/li>\n<li>Headers are sent on the front end, the login screen, the dashboard and REST responses \u2014 not just the front end.<\/li>\n<li>Safe defaults: only headers that suit essentially every site are on out of the box. CSP, HSTS, Permissions-Policy and CORS ship switched off with a recommended value pre-filled, because guessing those breaks real sites.<\/li>\n<li>Every value is validated against header injection before it is saved, and risky settings are flagged with an explanation rather than silently accepted.<\/li>\n<li>HSTS is only sent over a secure connection and only after you confirm your subdomains are ready.<\/li>\n<\/ul>\n\n<h4>HTTPS enforcement<\/h4>\n\n<ul>\n<li>Redirects insecure requests, honouring forwarding headers from trusted proxies so sites behind Cloudflare or a load balancer do not loop.<\/li>\n<li>Form submissions redirect with <code>307<\/code>, so no data is lost.<\/li>\n<li>Cannot be switched on from an insecure dashboard session, which is the usual way people lock themselves out.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin files to <code>\/wp-content\/plugins\/wps-protect-login-url-security-headers\/<\/code>, or install through the Plugins screen.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Go to <strong>WPS Protect<\/strong> in the dashboard menu.<\/li>\n<\/ol>\n\n<p>Activation does not change your login URL. Nothing moves until you set a slug yourself, and the plugin issues a rescue URL at that moment.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"what%20happens%20if%20i%20forget%20my%20login%20url%3F\"><h3>What happens if I forget my login URL?<\/h3><\/dt>\n<dd><p>Use the rescue URL shown when you saved the slug. If you no longer have it, add <code>define( 'WPSP_DISABLE_LOGIN_GATE', true );<\/code> to <code>wp-config.php<\/code>, or run <code>wp wpsp login-url<\/code> over WP-CLI. Deactivating the plugin also restores <code>wp-login.php<\/code>, and your settings survive deactivation.<\/p><\/dd>\n<dt id=\"will%20this%20break%20my%20caching%20plugin%3F\"><h3>Will this break my caching plugin?<\/h3><\/dt>\n<dd><p>Login and REST requests are never cached, so brute-force protection and the login gate are unaffected. Security headers are sent by PHP, so on a page served from a full-page cache they may be served by your cache layer instead. If your host serves cached pages without invoking PHP, set the headers at the server level too.<\/p><\/dd>\n<dt id=\"does%20it%20work%20behind%20cloudflare%3F\"><h3>Does it work behind Cloudflare?<\/h3><\/dt>\n<dd><p>Yes, but configure your proxy ranges under the brute-force settings first. Until you do, the plugin deliberately declines to block by IP address rather than risk locking out every visitor sharing a proxy address.<\/p><\/dd>\n<dt id=\"will%20it%20conflict%20with%20another%20security%20plugin%3F\"><h3>Will it conflict with another security plugin?<\/h3><\/dt>\n<dd><p>Enable \"do not overwrite headers already set\" if something else is already sending headers. Running two plugins that both hide the login URL is not supported \u2014 pick one.<\/p><\/dd>\n<dt id=\"i%20upgraded%20from%201.x.%20what%20changed%20for%20me%3F\"><h3>I upgraded from 1.x. What changed for me?<\/h3><\/dt>\n<dd><p>Your settings are migrated automatically, including your login slug, HTTPS setting and any header values you saved. One thing intentionally does not carry over: 1.x applied a hard-coded <code>Content-Security-Policy: default-src 'self'<\/code> whenever you had not set one, which blocked inline scripts and styles on most sites. That fallback is gone. If you want a CSP, set one explicitly on the Security Headers tab.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.0.0<\/h4>\n\n<p>Complete architectural rewrite, plus WordPress 7.1 compatibility.<\/p>\n\n<p><strong>Fixed (critical)<\/strong><\/p>\n\n<ul>\n<li>Administrators were locked out of <code>\/wp-admin\/<\/code> whenever a custom login URL was set. The capability check that guarded the dashboard was inverted: signed-in administrators were blocked while lower-privileged users were let through.<\/li>\n<li>Every <code>admin-ajax.php<\/code> request returned 404 with a custom login URL set, breaking the block editor, media uploads, WooCommerce and any plugin using AJAX.<\/li>\n<li>The default <code>Content-Security-Policy: default-src 'self'<\/code> was applied to every site that had not visited the settings screen, blocking inline scripts and styles used by WordPress core, most themes and most plugins.<\/li>\n<li><code>Access-Control-Allow-Origin: *<\/code> is no longer sent by default.<\/li>\n<li>The HTTPS redirect built its target from the unvalidated <code>Host<\/code> request header, allowing an open redirect, and issued it as a permanently cached <code>301<\/code>.<\/li>\n<li>Requests were matched with an unanchored substring search, so any URL merely containing <code>wp-admin<\/code> or <code>wp-login.php<\/code> \u2014 including ordinary posts and uploads \u2014 returned 404.<\/li>\n<li><code>exit<\/code> inside the <code>wp_logout<\/code> action aborted password resets and any programmatic logout.<\/li>\n<li>Password-protected posts (<code>action=postpass<\/code>) no longer return 404.<\/li>\n<li>Settings are no longer deleted when the plugin is deactivated. Deletion now happens on uninstall, as it should.<\/li>\n<\/ul>\n\n<p><strong>Added<\/strong><\/p>\n\n<ul>\n<li>Brute-force protection: escalating lockouts, per-IP and per-username tracking, allowlist and blocklist, honeypot, generic error messages, and <code>429<\/code> with <code>Retry-After<\/code>. Covers the login form, XML-RPC, REST, Application Passwords, WooCommerce and BuddyPress.<\/li>\n<li>Emergency access: one-time rescue URL, a <code>wp-config.php<\/code> constant, and WP-CLI commands.<\/li>\n<li>Login slug validation against reserved paths, existing content and post-type archives.<\/li>\n<li>Security headers now apply to the login screen, dashboard and REST API, not only the front end.<\/li>\n<li>Content-Security-Policy-Report-Only, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy and X-Permitted-Cross-Domain-Policies.<\/li>\n<li>Header value validation that rejects response splitting, with advisory warnings for risky values.<\/li>\n<li>Proxy-aware client IP resolution with configurable trusted ranges, and proxy-aware HTTPS detection.<\/li>\n<li>WP-CLI: <code>wp wpsp login-url<\/code>, <code>set-slug<\/code>, <code>disable-gate<\/code>, <code>rescue<\/code>, <code>unlock<\/code>, <code>lockouts<\/code>, <code>audit<\/code>.<\/li>\n<li>Audit log with retention policy and an IP anonymisation option.<\/li>\n<\/ul>\n\n<p><strong>Changed<\/strong><\/p>\n\n<ul>\n<li>Rewritten as PSR-4 classes under <code>src\/<\/code>, separated into Login, Headers, SSL, Brute-force, Admin, Support, CLI and Compat modules.<\/li>\n<li>Settings are saved through <code>admin-post.php<\/code> with capability checks, nonce verification and a POST\/Redirect\/GET cycle, so refreshing no longer resubmits.<\/li>\n<li><code>X-XSS-Protection<\/code> now defaults to <code>0<\/code>, matching current OWASP guidance. The previous <code>1; mode=block<\/code> enabled a deprecated browser filter with vulnerabilities of its own.<\/li>\n<li>HSTS requires an explicit confirmation and a secure connection before it is sent.<\/li>\n<li>Minimum requirements raised to WordPress 6.2 and PHP 7.2. WordPress 6.2 is required for the <code>%i<\/code> identifier placeholder in <code>$wpdb-&gt;prepare()<\/code>, which the brute-force log uses to quote table names.<\/li>\n<\/ul>\n\n<p><strong>Compatibility<\/strong><\/p>\n\n<ul>\n<li>Tested against WordPress 7.1. The plugin ships no JavaScript or CSS, so the 7.1 iframed editor, <code>@wordpress\/components<\/code>, jQuery UI 1.14.2 and client-side media processing changes do not apply to it. Applying security headers across <code>wp-admin<\/code> is a separate, off-by-default option, because a restrictive Content-Security-Policy can interfere with the now always-iframed editor.<\/li>\n<li>The <code>wpsplu_options<\/code> record, the <code>wpsplu<\/code> admin page slug and its tab URLs are all preserved. Removed 1.x functions remain declared as no-op shims that raise a deprecation notice rather than a fatal error.<\/li>\n<\/ul>\n\n<h4>1.1<\/h4>\n\n<ul>\n<li>Added Permissions-Policy header support<\/li>\n<li>Improved Content-Security-Policy configuration<\/li>\n<li>Enhanced login URL handling<\/li>\n<li>Added tab-based admin interface<\/li>\n<li>Updated security headers with recommended values<\/li>\n<\/ul>\n\n<h4>1.0<\/h4>\n\n<ul>\n<li>Initial release with basic security features<\/li>\n<\/ul>","raw_excerpt":"Move your login URL, stop brute-force attacks, and send HTTP security headers on every response. Lightweight, cache-friendly, no bloat.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/233266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=233266"}],"author":[{"embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/junaid434"}],"wp:attachment":[{"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=233266"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=233266"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=233266"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=233266"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=233266"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ko.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=233266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}