HTML and PHP code on screen showing Authentication Failed error message, illustrating the WordPress REST API rest_cannot_create error

Fix WordPress REST API rest_cannot_create Error

Your script sends a POST to /wp-json/wp/v2/posts with credentials, and WordPress throws back rest_cannot_create with the message “Sorry, you are not allowed to create posts as this user” and HTTP 401 or 403. GET requests work fine. Reads succeed. Writes fail. This is one of the most common WordPress REST API errors, and almost every forum thread about it ends unresolved because the real cause is rarely where people look first. Here is the complete diagnostic path, in order of likelihood, with exact fixes for each.

Fast Diagnosis: Auth Problem or Capability Problem?

The error message is misleading. “Not allowed to create posts as this user” sounds like a permissions issue, but in roughly 70% of cases the request is arriving as an anonymous user because authentication silently failed. WordPress then checks the edit_posts capability for user ID 0, fails, and returns the same error you would get if a legitimate user lacked the capability. You need to determine which layer failed before you can fix anything.

The single most diagnostic signal: if your GET requests to /wp-json/wp/v2/posts return published posts without authentication (which is expected, since reads are public), but POST requests fail with 401, you are almost certainly dealing with a stripped Authorization header or a security plugin, not a role problem. If GET requests to authenticated endpoints like /wp-json/wp/v2/users/me also fail, authentication is broken entirely. If /wp-json/wp/v2/users/me works and returns the correct user but POST still fails, the issue is capability-based.

Symptom HTTP Status Likely Cause Fix Section
GET works, POST fails, no user ID returned 401 Authorization header stripped by server Cause 1
GET to /users/me returns user ID 0 401 Auth header not reaching PHP or wrong credentials format Cause 1 or 7
/users/me returns correct user, POST still fails 403 User role lacks edit_posts or publish_posts Cause 2 or 3
Works locally, fails on production 401 HTTPS requirement for Application Passwords Cause 4
Worked before installing a security plugin 401 or 403 WAF or security plugin blocking writes Cause 5
All REST requests fail, not just writes 401 rest_authentication_errors filter rejecting all requests Cause 6

Cause 1: Your Server Strips the Authorization Header

Server rack hardware with green status lights representing Apache and Nginx web server infrastructure where Authorization headers get stripped

This is the most common cause. Apache running PHP via mod_cgi or php-fpm strips the Authorization header before it reaches PHP by default. The request arrives at WordPress with no credentials, WordPress treats it as anonymous, and the rest_cannot_create error is returned even though your client is sending a valid Authorization: Basic ... header.

The signature symptom: GET requests succeed because public reads require no authentication. POST requests fail with 401 because writes require authentication, and the credentials never arrive. If you inspect $_SERVER['HTTP_AUTHORIZATION'] inside WordPress, it will be empty or absent.

How to confirm it

Add this temporary check to your theme’s functions.php or a mu-plugin, then make a POST request:

add_action( 'rest_api_init', function() { error_log( 'AUTH HEADER: ' . var_export( $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? 'MISSING', true ) ); }, 1 );

Check wp-content/debug.log after the request. If it logs MISSING, the header is being stripped. If it logs the base64 string, the header is arriving and the problem is elsewhere.

The fix for Apache (.htaccess)

Add the following lines to your .htaccess file, above the standard WordPress rewrite rules. The order matters because the [L] flag on later rules would prevent this one from executing if placed after them:

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Alternatively, if you are on Apache 2.4.13 or later, you can use the cleaner directive in your server config or virtual host block (not .htaccess):

CGIPassAuth On

Both approaches accomplish the same thing: they tell Apache to pass the Authorization header through to PHP. The SetEnvIf method works on older Apache versions as well. After applying the fix, clear any opcode cache and retest. This is also documented in the official WordPress REST API FAQ.

The fix for Nginx

Nginx does not always pass the Authorization header to PHP-FPM by default. Add this line inside the fastcgi section of your server block, typically in the location ~ .php$ block:

fastcgi_pass_header Authorization;

Then reload Nginx with sudo nginx -s reload or sudo systemctl reload nginx. If you are on a managed host that does not give you root access (WP Engine, Kinsta, Flywheel), contact support and ask them to enable passing of the Authorization header. Some hosts already handle this at the platform level, but some older configurations do not.

Cause 2: The User Role Lacks publish_posts

If authentication is working correctly (you can confirm by hitting /wp-json/wp/v2/users/me and getting the right user ID back), the next most common cause is that the authenticated user’s role does not include the edit_posts capability, which is the minimum required to create posts via the REST API.

WordPress checks two capabilities when creating a post via REST: create_posts (which maps to edit_posts by default) and then, if you specify status: "publish", publish_posts. If the user lacks edit_posts, the request fails immediately with rest_cannot_create before it even gets to the publish check. If the user has edit_posts but not publish_posts, the failure depends on the status value, which is covered in the next section.

Here is how the default WordPress roles map to the capabilities relevant to REST API post creation, per the WordPress Roles and Capabilities documentation:

Role edit_posts publish_posts Can create via REST? Can publish via REST?
Subscriber No No No No
Contributor Yes No Yes (draft only) No
Author Yes Yes Yes Yes (own posts)
Editor Yes Yes Yes Yes
Administrator Yes Yes Yes Yes

How to confirm it

Hit /wp-json/wp/v2/users/me?context=edit with your credentials. The response includes the user’s roles. Cross-reference with the table above. If the user is a Subscriber or Contributor and you are sending status: "publish", the error is expected behavior.

The fix

Either upgrade the user’s role to Author or above, or add the specific capability programmatically:

$user = get_user_by( 'login', 'your_username' );
$user->add_cap( 'edit_posts' );
$user->add_cap( 'publish_posts' );

For automation workflows, creating a dedicated user with the Author role is the safest approach. You get the minimum capability set needed for publishing without granting admin-level access.

Cause 3: Requesting status=publish Without the Capability

This is a subtle variation of Cause 2. The user authenticates, has edit_posts, and can create drafts. But the request specifies "status": "publish" and the user’s role lacks publish_posts. WordPress returns the same rest_cannot_create error, which makes it look like an authentication failure when it is actually a capability mismatch on the status field.

How to confirm it

Send the same POST request but with "status": "draft" instead of "status": "publish". If the draft request succeeds but the publish request fails, this is your cause.

The fix

Two options. Option A: change the status to "draft" in your request body and publish separately after review. This is the approach most automation tools should use when working with Contributor-level accounts. Option B: ensure the authenticated user has the publish_posts capability by upgrading to Author or above.

If you are building a workflow that creates drafts for editorial review, always send "status": "draft" and avoid the issue entirely. Publishing can be a separate step performed by a user with the correct capability.

Cause 4: Application Passwords and the HTTPS Requirement

Application Passwords, introduced in WordPress 5.6, are the recommended authentication method for REST API automation. They require HTTPS. WordPress checks is_ssl() when determining availability, and if the request arrives over plain HTTP, Application Passwords are silently disabled. The request fails with 401, and depending on your setup, you may not get a clear error explaining why.

How to confirm it

Send a GET request to your site’s root REST endpoint: https://example.com/wp-json/. Look at the authentication key in the response. If Application Passwords are available, you will see an application-passwords object inside it. If that key is missing or the authentication object is empty, Application Passwords are disabled on your site.

Also check the user profile page in wp-admin. Go to Users, edit the user, and scroll to the Application Passwords section. If you see the message “The application password feature requires HTTPS, which is not enabled on this site,” that confirms the issue.

The fix

Properly configure HTTPS on your site with a valid TLS certificate. This is the correct production fix. WordPress determines SSL status by checking $_SERVER['HTTPS'] or the port number via is_ssl(), so make sure your server or reverse proxy is setting these correctly.

If you are in a local development environment and cannot use HTTPS, you can force Application Passwords availability with a filter in wp-config.php or a mu-plugin:

add_filter( 'wp_is_application_passwords_available', '__return_true' );

For local environments specifically, you can also define the environment type:

define( 'WP_ENVIRONMENT_TYPE', 'local' );

This automatically enables Application Passwords on local environments. Do not use the __return_true filter in production, as it allows credentials to be sent over unencrypted connections where they can be intercepted.

When is_ssl() Returns False but Your Site Is on HTTPS

This is the case the standard advice misses. Your site loads over HTTPS in the browser, the padlock is there, and WordPress still refuses to enable Application Passwords. The two messages you will see are:

Application passwords require HTTPS or WP_ENVIRONMENT_TYPE set to “local”.

and, in Tools → Site Health:

The native WordPress function is_ssl() returned false.

Both mean the same thing. PHP does not know the request arrived over TLS. This happens when TLS terminates before PHP ever sees the request. A reverse proxy, a load balancer, or Cloudflare accepts the HTTPS connection and forwards to your origin over plain HTTP. is_ssl() reads $_SERVER['HTTPS'], which the proxy never sets, so it returns false and Application Passwords stay disabled. Your certificate is fine. The handoff is the problem.

How to confirm it

Compare the forwarded protocol header against what PHP sees. Drop this in a mu-plugin temporarily:

error_log( print_r( array(
    'HTTPS'             => $_SERVER['HTTPS'] ?? 'unset',
    'X-Forwarded-Proto' => $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'unset',
    'is_ssl()'          => is_ssl() ? 'true' : 'false',
), true ) );

If X-Forwarded-Proto is https while HTTPS is unset and is_ssl() is false, this is your cause.

The fix

Set $_SERVER['HTTPS'] from the forwarded header in wp-config.php. It must go above the require_once ABSPATH . 'wp-settings.php'; line. Below that line it runs after WordPress has already decided, and nothing changes.

if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
    && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
    $_SERVER['HTTPS'] = 'on';
}

Only trust that header when a proxy you control sets it. If clients can reach your origin directly, anyone can send X-Forwarded-Proto: https over plain HTTP and switch Application Passwords back on over an unencrypted connection. Restrict origin access to your proxy first, then apply the fix.

The Cloudflare Flexible SSL trap

If you are on Cloudflare, check your SSL/TLS encryption mode before you touch wp-config.php. On Flexible, the browser-to-Cloudflare hop is encrypted and the Cloudflare-to-origin hop is genuinely plain HTTP. WordPress is not wrong in that case. It is correctly reporting that the request reached it unencrypted.

Setting the header on Flexible mode hides the warning and leaves your Application Password travelling in clear text on the last hop. Switch the mode to Full (strict) and install a Cloudflare origin certificate on your server. Once the origin actually serves HTTPS, apply the wp-config.php fix above and the error resolves for the right reason.

Cause 5: Security Plugins Blocking REST Writes

Cursor hovering over Security menu item on a computer screen, representing WordPress security plugin settings that can block REST API write requests

Security plugins with Web Application Firewall (WAF) features can block REST API write requests while allowing reads through. The WAF sees the POST body containing HTML or JSON, flags it as suspicious, and returns a 401 or 403 before the request even reaches WordPress core. Wordfence, Solid Security (formerly iThemes Security), and All In One WP Security are the most common offenders.

How to confirm it

Temporarily deactivate every security plugin on your site and retest the POST request. If it succeeds, reactivate the plugins one by one, retesting after each activation, to identify which plugin is blocking the request.

For Wordfence specifically, check Wordfence > Tools > Live Traffic for blocked requests matching your API call. The block entry will show the reason and provide an option to add the request parameter to the allowlist. You can also switch the WAF to Learning Mode temporarily (Wordfence > Manage WAF > Web Application Firewall Status > Learning Mode), make your API calls, then switch back to Enabled and Protecting. This trains the WAF to recognize your legitimate requests.

The fix

For Wordfence: add the IP address of your automation tool or server to the allowlist under Wordfence > All Options > Advanced Firewall Options > Allowlisted IP addresses. Alternatively, add specific request parameters to the firewall allowlist via Live Traffic.

For Solid Security: check the Local Brute Force Protection and Network Brute Force Protection settings. If your automation tool makes rapid sequential requests, the brute force protection may flag it. Add the source IP to the allowlist.

For All In One WP Security: check the REST API settings under the firewall section. Some configurations block or restrict REST API access entirely.

Cause 6: The rest_authentication_errors Filter

A rest_authentication_errors filter in your theme, a plugin, or a mu-plugin can reject REST API requests before they reach the post creation logic. This filter is commonly used to require authentication for all REST API requests or to disable the REST API for non-logged-in users. If the filter returns a WP_Error, every unauthenticated REST request fails, including your POST with Basic Auth if the filter does not recognize Application Passwords.

How to confirm it

Search your codebase for rest_authentication_errors:

grep -r "rest_authentication_errors" wp-content/

Check your active theme’s functions.php, any mu-plugins in wp-content/mu-plugins/, and active plugin files. Look for add_filter( 'rest_authentication_errors', ... ) callbacks.

The fix

If a filter is rejecting authenticated requests that use Application Passwords, the callback likely checks is_user_logged_in() which returns false for REST API requests authenticated via Basic Auth (Application Passwords do not set the logged-in cookie). The filter needs to respect the $result parameter passed to it. Here is the correct pattern, from the WordPress REST API Handbook:

add_filter( 'rest_authentication_errors', function( $result ) {
if ( true === $result || is_wp_error( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_not_logged_in', 'You are not logged in.', array( 'status' => 401 ) );
}
return $result;
} );

The key detail: the first if check passes through any existing authentication result. If Application Passwords already authenticated the user, $result will be true and the filter returns early. Without that guard, the is_user_logged_in() check fails for Application Password requests because they authenticate via Basic Auth headers, not cookies, and is_user_logged_in() returns false.

Isolating the Cause With curl

Computer monitor displaying terminal output and command line data in green text, representing curl command execution for WordPress REST API debugging

Use this curl-based diagnostic sequence to isolate the problem layer by layer. Replace example.com, your_username, and your_app_password with your actual values. Use an Application Password, not the user’s regular login password.

Step 1: Verify the REST API is reachable

curl -i https://example.com/wp-json/

You should get HTTP 200 with a JSON response listing available namespaces. Check the authentication key for application-passwords availability. If this fails, your server is not routing requests to WordPress correctly.

Step 2: Verify authentication is working

curl -i -u "your_username:your_app_password" https://example.com/wp-json/wp/v2/users/me

If this returns the user object with the correct ID, authentication is working and the problem is capability-based (go to Cause 2 or 3). If it returns 401 with rest_cannot_view_user or user ID 0, authentication is failing (go to Cause 1, 4, or 7).

Make sure the -u flag has a space between the username and password with a colon separating them: "username:password". A common mistake is omitting the space after -u or using the application password name instead of the WordPress username.

Step 3: Test post creation as a draft

curl -i -u "your_username:your_app_password"
-X POST
-H "Content-Type: application/json"
-d '{"title":"Test Post","status":"draft"}'
https://example.com/wp-json/wp/v2/posts

If this succeeds, the user has edit_posts but may lack publish_posts. Try the same request with "status":"publish". If publish fails but draft succeeds, the issue is Cause 3.

Step 4: Test with explicit Authorization header

If Step 2 failed, test with an explicit header to rule out curl’s -u flag doing something unexpected:

curl -i -H "Authorization: Basic $(echo -n 'your_username:your_app_password' | base64)"
https://example.com/wp-json/wp/v2/users/me

If this also returns 401, the Authorization header is not reaching PHP. Go to Cause 1.

A Note on Basic Auth Credential Format (Cause 7)

The Authorization header for Basic Auth must follow the format Basic . The word “Basic” must be followed by a space, then the base64-encoded string. A missing space between “Basic” and the token is a surprisingly common cause of silent authentication failure. The base64 string encodes username:password, where the username is the WordPress login name (not the display name) and the password is the Application Password (not the regular login password).

Application Passwords are generated in wp-admin under Users, edit the user, scroll to Application Passwords, enter a name, and click Add New Application Password. The generated password is shown once with spaces for readability. Use it without the spaces in your requests. Copy it immediately because it is not shown again.

If you are using curl’s -u flag, curl handles the base64 encoding for you. If you are constructing the header manually in code, you must base64-encode the username:password string yourself.

Quick Resolution Checklist

Work through these in order to resolve most rest_cannot_create errors in under 15 minutes:

  • Run curl -i -u "user:app_password" /wp-json/wp/v2/users/me to confirm authentication.
  • If 401, add the SetEnvIf Authorization fix to .htaccess (Apache) or fastcgi_pass_header Authorization (Nginx).
  • Confirm HTTPS is active and Application Passwords are available in the user profile.
  • Check the user’s role. Author or above for publishing, Contributor for drafts only.
  • Test with "status":"draft" vs "status":"publish" to isolate capability issues.
  • Deactivate security plugins and retest to rule out WAF blocking.
  • Search the codebase for rest_authentication_errors filters.
  • Verify the Authorization: Basic header has a space and uses the correct base64 format.

If you are automating WordPress content publishing and hitting this error, ClearPost handles authentication and publishing internally so you do not have to debug server configuration, role capabilities, or security plugin conflicts. The plugin authenticates using WordPress’s native systems and manages the full post creation pipeline. For everything else, the diagnostic steps above will resolve the rest_cannot_create error in the vast majority of cases.

Frequently Asked Questions

What does the rest_cannot_create error mean in WordPress REST API?

It means the authenticated user (or anonymous user if authentication failed) does not have the edit_posts capability required to create posts. Despite the message mentioning permissions, the most common cause is actually a stripped Authorization header that causes WordPress to treat the request as anonymous.

Why does GET work but POST fails with rest_cannot_create?

GET requests to public endpoints like /wp-json/wp/v2/posts do not require authentication, so they succeed even when the Authorization header is stripped. POST requests require authentication, and if the server strips the Authorization header before it reaches PHP, the request arrives as anonymous and fails with 401.

Do I need HTTPS for WordPress Application Passwords?

Yes. Application Passwords are only available over HTTPS by default. WordPress checks is_ssl() and disables Application Passwords for requests over plain HTTP. You can force availability in local development with add_filter(‘wp_is_application_passwords_available’, ‘__return_true’), but this should never be used in production.

Can security plugins block WordPress REST API write requests?

Yes. Security plugins with WAF features like Wordfence can block REST API POST requests that contain HTML or JSON payloads, flagging them as suspicious while allowing GET reads through. Deactivate security plugins and retest to confirm, then add the source IP to the plugin’s allowlist.

What is the difference between edit_posts and publish_posts in the REST API?

edit_posts allows creating and editing draft posts. publish_posts allows setting a post status to publish. A Contributor has edit_posts but not publish_posts, so they can create drafts via the REST API but cannot publish. If your request specifies status:publish and the user lacks publish_posts, you get rest_cannot_create.

Why does is_ssl() return false when my site uses HTTPS?

Because TLS is terminating at a proxy. A reverse proxy, load balancer, or Cloudflare accepts the HTTPS connection and forwards to your origin over plain HTTP, so PHP never sees $_SERVER['HTTPS']. Set it from $_SERVER['HTTP_X_FORWARDED_PROTO'] in wp-config.php above the wp-settings.php require line.

Is it safe to set $_SERVER[‘HTTPS’] from X-Forwarded-Proto?

Only when a proxy you control is the sole route to your origin. That header is client-supplied, so if your origin is reachable directly, an attacker can forge it over plain HTTP and re-enable Application Passwords on an unencrypted connection. Lock the origin down to your proxy first. On Cloudflare Flexible SSL the origin hop really is unencrypted, so move to Full (strict) rather than masking the warning.