Ok, if you’re reading this, I’m assuming you know what CORS means, so I won’t tell you that it stands for Cross Origin Resource Sharing. Or maybe I just told you.

Anyway, you want to enable it on your Apache server. Maybe, like me, you’re building an API-based web app. So you need some JavaScript to pull data from a remote server. (Or even, like in my case, a different subdomain on the same physical server.) It’s easy in Node.js, so it shouldn’t be hard in Apache.

So you google “apache enable cors”. The first result is from enable-cors.org. Wow, how relevant! Sounds so legit! And it says all you have to do is throw this somewhere:

Header set Access-Control-Allow-Origin "*"

So you put it in your httpd.conf file or .htaccess and boom done.


Except then you try it. And Firebug is all like: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://buckle.up.because.thisll.suck.org. This can be fixed by moving the resource to the same domain or enabling CORS.

And Chrome says: XMLHttpRequest cannot load https://howdare.youthink.thiswouldbe.easy. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://pain.and.suffering.org’ is therefore not allowed access.

But… you did it right. You did just what it said you should do. You even googled it a few more times and everyone says the same thingโ€”Just that one line of code and you’re supposed to be done! You’re supposed to be kicking back with some nachos now! Chrome even says that the header is there, for crying out loud!

Screenshot from Chrome


Yeah, no.

Disappointed face
Turns out there’s a friggin metric crapton more to it than that. I won’t go into all the details here, but there’s a lot. What I will do is give you a list of quick and dirty things to try.

Enable mod_headers

There’s a module that allows Apache to add things to the request/response headers. You’ll need that. Near the top-ish of your httpd.conf file, look for…

#LoadModule headers_module modules/mod_headers.so

(Mine was on line 115 in my Apache 2.4 setup.)

If yours has that hash/number/octothorpe/# sign at the beginning, remove it. As with any change to httpd.conf, you’ll need to restart Apache for this to take effect.


First, just putting that line in my .htaccess wasn’t working. I could see the header in Chrome (image above) but it was apparently being ignored for who-the-crap-knows-why. Adding “always” before “set” seemed to correct this. I also moved it from my .htaccess to httpd.conf, just above my virtual hosts section.

Header always set Access-Control-Allow-Origin "*"

You’ll also see different versions of this out there in the wild, some where it says to use header set, and others where it says header add. Both will work, but set is safer in this case because add can add multiple headers, which according to the CORS documentation is not allowed.

Depending on your situation, that might do it for you. But probably not. So…

Other headers

Try adding these three:

Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"

The 2nd one determines what headers your requesting server (the one trying to make the remote call) is allowed to send. You likely don’t need all of those, but I left in a bunch for the sake of example.

The 3rd one is super important. It determines what kind of RESTful calls your app is allowed to make. Again, you probably don’t need all of them. In fact, if you’re only doing GET requests, that’s the only one you need. But if you want to POST, then you need OPTIONS, too. More on that below.

GET works, but POST doesn’t: welcome to “preflights”

I won’t go into details here, but I will say that POSTs are different than GETs beyond the obvious ways. One way being that with POST, browsers do what’s called a “preflight” check. Basically, it sends a request before your actual POST, checking to see if it’s allowed to do what it’s trying to.

This was very confusing at first, because my GET calls were working fine, but when I tried to do a POST call, Chrome: 1. showed it as an OPTIONS request; and 2. returned 404 (or 400 or 403 or 500). The heck?

What’s happening is likely that your server is trying to respond to that OPTIONS preflight as a normal page fetch. It’s literally trying to serve up a page. In my case, the URL in question was a PHP script that was expected POST data and wasn’t getting it, so it barfed up a 403 error.

Making PHP return 200 OK for preflights

I had to make a script called blank.php, which contained nothing but some ranty comments. Then, over in .htaccess, I added this:

RewriteEngine On                  
RewriteRule ^(.*)$ blank.php [QSA,L]

What’s happening here is, whenever an OPTIONS request comes in (line 2), it redirects any-and-all of them to blank.php (line 3). Blank.php is, well, blank, so it returns an HTTP status of 200 OK. The browser then takes this as a successful attempt to discover what its OPTIONS are, learns from the other headers (Allow-Origin, Allow-Headers, Allow-Methods) that it’s allowed to do a POST, and then finally sends the real, actual, honest POST that you’ve been trying to get it to do this entire freakin’ time.

Hope that helped

Depending on your environment/needs, that might not be the end for you. If so, you have my sympathy. Either way, I hope this at least gets you a few steps closer.

28 replies


i lol’d


Have I told you lately that I love you?


Oh dude, I’ve been 2 days trying to work this out, it was the pre-flight shit bothering.
Thanks so much.
BTW. Also gotta load the rewrite module in apache for this to work


Im facing a similar issue with my WP installation but every time i run my page after inserting the CORS in the htaccess file it gets overwritten back to default.
Any clue?

Toastmaster Generalsays:

The first thing I’d try is to make sure you’re not putting your changes inside the WordPress zone of your htaccess. (Between “# BEGIN WordPress” and “# END WordPress”). If that doesn’t work, you could try making it read-only except for when you need to change it.


Hi there.. I a fighting with this problem. I followed completely your post and I still do not get it working.. ๐Ÿ™ Any help is welcome guys.. that is crazy with CORS.


Great blog. It resolved my issues (i have no experience in apache admin).
i have: load balancer -> reverse proxy -> wordpress
Might help others: the entries in .htaccess should be submitted on the host that gets the javascript call (if i’m not wrong, it worked for me, meaning on the reverse proxy site)


added to my sites-enabled with “always” fixed right up!


Thanks a lot !! saved me tons of time !


Great post, CORS is a pain and this was really helpful. Thanks!!


This information was a big help. Thanks.


Thanks, this was helpful


Oh man, finding this after struggling for two days with this nonsense, I just had to laugh. http://www.pain_and_suffering.com LOL


Thanks a lot!!!!!!!!!!!!!
you are too good

Ken Rileysays:

Thank you!!!!! ALWAYS


Hi guys,
Sorry we are in the middle of a Hackaton, doing a Ionic app with a back in PHP. good idea, right ?
we succeeded the get method but we struggle with the post. where shall we put the blank.php file ?



Thank a lot. Instead of creating blank.php, we can do this :

RewriteRule ^(.*)$ $1 [R=200,L,E=HTTP_ORIGIN:%{HTTP:ORIGIN}]]


Thx a lot for this great post.
I still have issues (I mean, javascript error returning 404) using CORS to a HTTP Apache server that protect its cgi-bin with AuthConfig (Basic type) mechanisms.

Any hints? I’m stuck on that ๐Ÿ™

Thx in advance !!!


After discovering “the whole web” I found your page. THANKS man, this saved my life.

Also, from another post I have a little other rewrite rule, which is shorter than that from Raja – I don’t know if it needs the E-variable. It works fine for me:

RewriteRule ^(.*)$ $1 [R=200,L]

And for your entertainment: I needed that to load remote images (same server but other port) into a pixi.js-WebGL-screen, because I “outsourced” all uploaded media, so that several websites at different server-locations could use them.

beautiful stuff, man. hilarious all the way.
did not help with getting $_POST to work as expected in php though. fuck knows why apache did not see params passed. another hour of alternate cursing and weeping and here’s the workaround if someone needs it.

$handle = fopen(‘php://input’,’r’);
$jsonInput = fgets($handle);
$params = json_decode($jsonInput,true);


5 days of futzing around, wondering who was doing what where with reverse proxies and caches and crap. This page + 10 minutes = WORKING. THANKS!

Vedu Harithssays:

For anyone looking for help—I know spent countless hours on this—and thank you for this!
My config:

AllowOverride none
Require all denied
Header always set Access-Control-Allow-Origin “*”
Header always set Access-Control-Max-Age “1000”
Header always set Access-Control-Allow-Headers “X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding”
Header always set Access-Control-Allow-Methods “POST, GET, OPTIONS, DELETE, PUT”

RewriteEngine On
RewriteRule ^(.*)$ $1 [R=200,L]

Eric Gusays:

AllowOverride None
Require all granted

Header always set Access-Control-Allow-Origin “*”
Header always set Access-Control-Allow-Methods “POST, GET, OPTIONS, DELETE, PUT”
Header always set Access-Control-Allow-Headers “x-requested-with, Content-Type, origin, authorization, accept, client-security-token”
Header always set Access-Control-Expose-Headers “Content-Security-Policy, Location”
Header always set Access-Control-Max-Age “600”

RewriteEngine On
RewriteRule ^(.*)$ $1 [R=200,L]

Thank you … you get a beer once you are in black forrest (Rothaus)
… now I need y beer myself ‘;-)


Excellent Post. I laughed ๐Ÿ™‚


Well done. I laughed ๐Ÿ™‚

Here is how I make my apache2 work well with Vue. In my vhost config (or in .htaccess) for a domain I put this:

Header set Access-Control-Allow-Origin “*”
Header add Access-Control-Allow-Headers “origin, x-requested-with, content-type”
Header add Access-Control-Allow-Methods “PUT, GET, POST, DELETE, OPTIONS”

I always set x-requested-with header in my POST requests too.

Hope the above helps.


Finally a good post on this CORS crap – thanks!

Leave a Reply

Your email address will not be published.