Updated October 2026 by Marius: rewritten for current WordPress and tested on WordPress 7.1. Originally published by Hardeep Asrani in 2014.
By default anyone can read a WordPress site. For an intranet, a client staging site or a members-only blog you usually want the opposite: every visitor logs in first, and after logging in lands on the page they originally asked for.
The snippet below does that with two hooks. The template_redirect action runs on every front-end request: pages, posts, archives, search, RSS feeds, the XML sitemap and robots.txt. The rest_authentication_errors filter covers the REST API, which never runs template_redirect and would otherwise keep serving your posts as JSON at /wp-json/wp/v2/posts.
The snippet
add_action( 'template_redirect', function () {
if ( is_user_logged_in() ) {
return;
}
// Pages anyone may see, by slug or ID.
if ( is_page( array( 'privacy-policy' ) ) ) {
return;
}
auth_redirect();
}, 1 );
add_filter( 'rest_authentication_errors', function ( $result ) {
if ( ! empty( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_login_required', 'You must be logged in to use the REST API.', array( 'status' => 401 ) );
}
return $result;
} );Code language: PHP (php)
auth_redirect() is the same function WordPress uses to protect /wp-admin/. It sends the visitor to wp-login.php with the current address in redirect_to, so after logging in they land back where they started. The login page itself keeps working, because it never runs template_redirect.
The 1 at the end of the first hook matters. Without it, WordPress’s own canonical redirect runs first, and a logged-out visitor who opens /?author=1 is redirected to /author/your-username/, which gives away the username before the login screen appears.
Keep some pages public
Add the slugs or IDs of pages anyone should be able to see, such as a privacy policy or a contact page, to the is_page() list: is_page( array( 'privacy-policy', 'contact', 42 ) ). Everything else still asks for a login.
What this does not protect
- Uploaded files. Images and PDFs in
/wp-content/uploads/are served by the web server without loading WordPress, so anyone with a file’s URL can still open it. Protecting media needs server rules or a plugin that serves uploads through PHP. - Open registration. If Settings > General > Membership: Anyone can register is ticked, a visitor can create an account from the login screen and then see everything. Leave it off unless that is what you want.
Prefer a plugin?
Force Login by Kevin Vess does the same job with filters for an allowlist and a custom redirect. It is actively maintained (last updated June 2026) and has more than 30,000 active installs.
Where to add this code
Add the snippet with a code snippets plugin instead of your theme’s functions.php, so it survives theme updates and you can switch it off without FTP. This guide shows how with the free Code Snippets plugin. On my own sites I use WPCodeBox, which keeps snippets in a cloud library so the same snippet can go on several sites. (Marius)
Disclosure: the WPCodeBox link is an affiliate link. If you buy through it, I earn a commission at no extra cost to you.

How do i retrict login to all pages except the home?
This function doesn’t seems to work anymore. I’ll do some research later on to find out why it’s not working. Is it working on your site?
Any update on this ? Thx!
Never got time to do some research on this issue. Is it working for you?
I have not tried yet. I think i will now rather install the plugin http://wordpress.org/plugins/wp-force-login/
Thanks for sharing that plugin. I just looked into that plugin and updated the article with a code which works :)
Thank you!
Hey, I just added this code. Works really nice, BUT when you click “Lost your password?” on wp-login.php, you can’t get there now unfortunately. Any idea how to solve this?
This works good for internal pages/posts, but not for the home page. How can this same functionally apply to the home page (is_home or is_front_page)?
I think you must place code to template_redirect hook rather than init. because if you placed on init action, there will block WP Rest API also.