All login credentials transferred over plain HTTP can easily be sniffed by an MITM attacker, but is is not enough to encrypt the login forms. If you are visiting plain HTTP pages while logged in, your session can be hijacked, and not even two-factor authentication will protect you. To protect all info sent between your visitors – which includes you – and your web server, we will redirect all requests that are coming over plain HTTP to the HTTPS equivalent.
It is not really necessary to use HTTPS for absolutely all requests, but it makes your life much easier to just handle one scheme and redirect all plain HTTP traffic to the equivalent HTTPS resource. So please make sure you setup HTTPS for the same hostname that you use for plain HTTP. Do NOT use secure.example.com
if your regular hostname is example.com
or www.example.com
. The only difference should be the scheme – nothing else. This will save you from a lot of headaches further down the road.
- Setup HTTPS on Nginx
- Optimize HTTPS on Nginx and get an A+ score on the SSLlabs test.
- Optionally, set up HTTP Public Key Pinning (HPKP)
- Redirect all HTTP traffic to HTTPS in your Nginx config:
server { listen 80 default_server; listen [::]:80 default_server; server_name _; return 301 https://$host$request_uri; }
Now all traffic for http://example.com/foobar
is redirected to https://example.com/foobar
. Please note that while this works fine for GET requests, the postdata is not sent to the new URL for POST requests. This is usually not an issue if you’re using WordPress – at least not if your website is coded somewhat properly – as all your forms should use the URL WordPress is configured to use.
The redirect response is sent with the HTTP status code 301, which tells the browser (and search engines) that this a permanent redirect. This makes the browser remember the redirect, so that next time they visit, the browser will do the redirect internally. If you set the HSTS header – which you should – the browser will even do this for every single request to your domain.
Note that the above is a very general purpose Nginx config that will redirect all hostnames on the server. You are free to specify the specific hostnames you want to have redirected. Also: If you’re a little paranoid – which is not a bad thing in web security – you will note that it’s using the Nginx $host
variable. This variable can be set by the HTTP Host header – provided by the client. It is most likely safe to use in this manner, but as a principle, it’s better to play it safe by using variables we set ourselves:
server { listen 80 default_server; listen [::]:80 default_server; server_name example.com www.example.com; return 301 https://$server_name$request_uri; }
We do have to use the $request_uri
variable – which we have very little control over. To remove malicious request URIs, you should look into getting a WAF (Web Application Firewall).
Not working if the https is handled by a proxy like Cloudflare, since the server listens on port 80 and not 443 (with the flexible ssl)
In that case, your proxy would normally handle the redirection from HTTP to HTTPS.
BTW, if you’re using Cloudflare, you should use “Full SSL” instead of “Flexible SSL”. There’s no reason why you’d want the traffic between Cloudflare and you to be unencrypted. Use a certificate from Let’s Encrypt or a self-signed one.
If you for some reason can’t handle the redirection in the proxy, or absolutely don’t want to setup HTTPS on your own server, your proxy should set a header indicating whether the request came over HTTP or HTTPS. Cloudflare sets
X-Forwarded-Proto
.Bjørn,
thanks for your article, and for letting me know Let’s encrypt.. I needed a way to obtain free but valid certificates for the platform I’m developing, and didn’t know that project!
You saved my day!
Paolo
Even better, CloudFlare generates their own certificates on your behalf, just for the connection between them and your server — no need to use Let’s Encrypt, since it’s CloudFlare’s certificate that will be presented to the visitor’s browsers anyway, not yours.
You still need a cert on your server to use “Full SSL” or “Strict SSL” (which you should).
Cloudflare has started generating origin certificates for this very purpose, and it’s available on the free plan. Security tab -> origin certs
Indeed, the same certificate seems to be shared by 50 other websites or so, but the whole setup works wonderfully well.
Thanks :) I keep forgetting that! I was scratching my head trying to figure out what I did wrong this time to get all those redirects… and of course, I had forgotten about the ‘Full’ setting on CloudFlare!
try:
server {
listen 80;
return 301 https://$server_name:80$request_uri;
}
That will recurse to itself!!
… no it will not. 3 things can make a unique request. IP OR Port OR hostheader value.
Http and https use different ports.
https://$server_name:80
You are redirecting to Port 80, but telling the browser to establish a TLS session. However the Nginx server is listening for unencrypted connections.
This may not quite redirect to itself, but will not work.
hi,
set in your nginx as default ssl on port 443.
You can combine this into one server block like so:
server {
listen 80;
listen 443 default_server ssl;
}
Sure, but then you would have to check for the
$scheme
being used:I like to keep separate virtualhosts with different logic in different
server
blocks.this work quick for me
Hey Bjørn! First off, thank you very much for writing this tutorial. I’m not quite sure if you noticed, but using the second method always results in a redirect to the first listed “server_name”, i.e.
server_name example.com http://www.example.com;
return 301 https://$server_name$request_uri;
sends me to “https://example.com” even if “www.example.com” was entered. The same is true for other subdomains, i.e.
server_name http://www.example.com test.example.com (…);
return 301 https://$server_name$request_uri;
always results in a redirect to “https://www.example.com”. I’ve searched around a bit and found the following comment on https://serverfault.com/questions/67316/in-nginx-how-can-i-rewrite-all-http-requests-to-https-while-maintaining-sub-dom
“Note that you must use $host instead of $server_name if you’re using subdomains.”
If that is really so, you might want to add a little note to your post. My solution (since I am more than a bit paranoid :p) was to enter each subdomain in its own server definition and to reverse the example.com / http://www.example.com order so that entering the URL in the browser leads to the expected result.
If you’re paranoid, you don’t have to use a server definition per hostname, but you can use a catchall server block for unrecognized hostnames and use the
$host
variable in a server block where you specify your valid hostnames:Hello Bjørn,
thank you very much for this simple paranoid setting. I set up my server and got flooded with very suspicious requests.
I think it is good time to be paranoid.
Thank you again for the whole article and the GIST lower! I use it as my go-to config now.
Hi Bjørn, I’ve been struggling for hours to find a solution to have one nginx host file which handles domain.tld and http://www.domain.tld with and without https and redirects all of them to use https and www
could you help me out there? No combination of above examples worked for me :-(
Sorry you’ve been struggling for so long. Will one of these solve what you’re trying to achieve?
3 different server blocks (I usually go with this approach):
https://gist.github.com/bjornjohansen/b5a763a57a460edac47c8560502f6400
2 server blocks and an
if
to check the HTTPHOST
header:https://gist.github.com/bjornjohansen/02429a0bda79d3e8230ea05573965776
Thanks Bjørn, that was a really fast reply :-)
The solution with 3 server blocks looks great but I have a few questions:
Why does the second server block not contain root /whatever/your/webroot/is; – is that an omission or on purpose?
Here is my current vhost file according to your instructions except I’m not sure what all goes into the second server block: http://pastebin.com/fYq2CjLf
The second block is to redirect https://domain.tld to https://www.domain.tld
Sure but what does go in here: # include your HTTPS config here ?
A complete copy of everything in the 3rd block? I tried that and the result was a warning about a wrong certificate so I had to point it to a non-www certificate to get the redirect to kick in.
Anyway, thanks for helping out so far, I have several open threads on different forums waiting for help so hopefully I can figure this out soon :-)
Brilliant thanks Bjorn. You saved me hours.
Hmm,
I think a have a big problem with understanding how to properly set up things combining different domains, ssl certificate and cloduflare.
My setup:
several tlds / domain variations
letsencrypt
cloudflare ssl full
what I would like to accomplish:
redirect all domain/tlds from non-www to (example) https://www.domain.de
What would be the option for this?
Hi Bjørn, first of all , thank you for your wonderful sharing. I also have a question.
Here is the deal:
Actually , our website support http and https, and we set the redirect in nginx.conf
server_name example.com;
return 301 https://www.example.com$request_uri;
but the magic is happen:
http://example.com —-> https://www.example.com :it works
https://example.com —->https://www.example.com : it doesn’t work..
Hi,
I have also a similar issue…
http://example.com —-> https://www.example.com :it works
https://example.com —->https://www.example.com : it doesn’t work..
Did you solve it ?
If yes, can you share it ?
Regards,
Soumitra
Add following code in nginx.conf file or file in which your server configuration is resides in server with 443 section.
‘
if ($host = example.com) {
return 301 https://www.example.com$request_uri;
}
‘
This will help you https://example.com —->https://www.example.com redirection.
Hello, do you have a way to do this when the server only runs on port 80 and is using an AWS load balancer? In apache we would use
“`X-Forwarded-For
X-Forwarded-Proto
X-Forwarded-Port“`
example
“`RewriteEngine on
RewriteCond %{HTTP:X-Forwarded-Proto} ^http$
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} R=301,L“`
thanks got your site in first result and used your code. it worked.
Hi i went to https but i can not make 301 redirect
This is all good, but for one very important point: When setting up any web server, first use 302 (temporary) redirects. Make sure everything works over the course of a week, then *only* change them to 301 (permanent) if you are absolutely sure the change is permanent. Once 301 redirects get into the world of DNS, they are there to stay.
I made this mistake on my web site. Luckily it was not a big deal, but I decided later that the ‘www’ was superfluous.
Don’t use 301 or 302 redirects for http to https conversion, because POST’s get redirected to GET’s in this way. If you have an API running, this will break it.
Use 307 or 308 redirects, as they will let the browser send the exact same method+data to the redirected url.
307 and 308 seem to be well supported by all browsers now (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308)
That’s interesting! Thank you for the tip :-)
It should be noted that while this is good advice for 307, it seems 308 is currently not supported by nginx: https://trac.nginx.org/nginx/ticket/877
In the last version (1.13) nginx support 308 redirect
I would like to redirect all https traffic to an app instance that will use port 8000. Could your code here be changed to accomplish that?
Do you want to redirect the visitors to port 8000, or proxy/forward the traffic to port 8000?
If you want to redirect them:
return 301 https://example.com:8000$request_uri;
If you want to proxy them (forward the traffic):
How can this be done without sending a 301?
Hi,
My apache has something like :
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]
The redirect may or mayn’t be permanent in my case. Is this possible in nginx?
Thanks.
Your Apache config will send a 302 redirect. So in Nginx you would use:
return 302 https://$server_name$request_uri;
Used the exact same code verbatim. Broke my website. Not a sysadmin so my knowledge is very limited. Just following awesome guides like yours. I also have redirect happening on .htaccess but that only works for my root domain. If I use http:mydomain.com/path it doesn’t redirect to https version of the same. Will appreciate you guiding me through it.
If you’re using .htaccess, you’re using Apache, not Nginx.
Hi Bjørn, first of all, thank you for your wonderful articles!
Can you help me? I spent many hours and efforts to set up nginx configuration, but I still have an issue. Below is my simplified configuration.
root /var/www/public;
index index.php index.html index.htm;
ssl_certificate /home/oleg/ssl/www.example.com.chained.crt;
ssl_certificate_key /home/oleg/ssl/www.example.com.key;
# disable ssl
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# optimizing the cipher suites
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
# redirect non-www to www version
server {
server_name example-en.com;
rewrite ^(.*) https://www.example-en.com$1 permanent;
}
server {
server_name example-es.com;
rewrite ^(.*) https://www.example-es.com$1 permanent;
}
server {
server_name example-pt.com;
rewrite ^(.*) https://www.example-pt.com$1 permanent;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
listen 443 ssl;
server_name http://www.example-en.com http://www.example-es.com http://www.example-pt.com;
# add trailing slash
if ($request_method = “GET” ) {
rewrite ^([^.]*[^/])$ $1/ permanent;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param HTTPS on;
}
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|svg|woff|woff2|ttf)\$ {
expires 1M;
access_log off;
add_header Cache-Control “public”;
}
}
http://example-en.com/ -> https://www.example-en.com/ -> success redirect
https://example-en.com/ -> https://www.example-en.com/ -> success redirect
http://www.casinobonusesnow.com/ -> http://www.casinobonusesnow.com/ -> fail
How can I change my config to enable redirection from urls starting with http://www. to https version?
I wanted to do proxy pass from https front end site to back end siete https . both application are in some other servers. but urls can be accessible from public.
I have tried all above
My exact requirement is….
I have xyz.com site but my backend server abc.com, but user should always face xyz.com.
Can you please help me out
A redirect is not what you are looking for. proxy_pass is what you want.
I need to configure following scenario in nginx — xyz.com site my front end url and my backend server abc.com, but user should always face xyz.com.
xyz.com is public which enabled https
abc.com is private site it is also enabled https.
I need to expose only xyz.com globally and site hosted on abc.com
Use proxy_pass, and set x-forwarded-for.
On abc.com check for x-forwarded-for, and if it’s not there, do a 301.
server {
listen 80;
server_name 123.xxxx.com;
location / {
proxy_redirect https://123.xxxx.com https://abc.xxxxx.com;
proxy_pass https://123.xxxx.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
configure d above but not working.
it is saying upstream not found when i configure upstream getting following error nginx: [emerg] host not found in upstream https://123.xxxx.com.
Can you please help me,
I suggest you hire a professional or try a support forum like Server Fault.
hi, thanks for the awsome site here.
a question about how to set up http not force to https but just available
i have a wordpress set up with Nginx, those images will broken link if i turn http force to https, but just want to have https available.
please adivse hot to do that . thanks a lot.
This was a one minute change instead of what you’re suggesting:
https://support.cloudflare.com/hc/en-us/articles/200170536-How-do-I-redirect-all-visitors-to-HTTPS-SSL-
REALLY simple.
Like I wrote before: If you’re using a proxy like CloudFlare, your proxy would normally handle the redirection from HTTP to HTTPS.
Nice post !! thanks for share
Was struggling for ages trying to get the redirection to work and it just kept stumping me. I ended up putting the redirects in their own `server` block instead of in the default and that seems to have done the trick.
As an aside, I have nothing but problems with Cloudflare. There are a few benefits to it but mostly just headaches.
Thanks again Bjørn for this. Up until last night I knew nothing about nginx and now I have a load of new tricks up my sleeve! :D
Hi, I’m struggling to find a solution to my problem and this thread seemed a nice place for it for some reason (even more than NGinx website or stackoverflow).
What I want is simple. I have a NodeJS application calling APIs on a SSL protected website (let’s call it https://whatever.com).
What I need is to proxy the connection made to whatever.com through Nginx, but I couldn’t find a way to do it. It seems like I need a domain name, so I can install a proper certificate, but that seems a huge overkill for something so simple.
If it was port80->port80 it would be dead simple to do. But because the destination API server is SSL, it turns into a nightmare.
Your solution isn’t enough because all requests are handled with 301, and I really need them to be processed by Nginx, so I can collect stuff such as request times, and so on.
Any hints?
Doesn’t sound like a redirect is what you want, but rather a
proxy_pass
statement in a separatelocation
block.Tried all sorts of variations in config (location, upstream, proxy_pass included). Do you know if it is possible, because of the SSL and all?
Hello guys.
Can you please help a dummy?
I’m using Webuzo to manage a mew new dedicated server.
From it’s GUI I can add new .conf files for each domain I have.
I have successfully added one .conf file with this content only:
try_files $uri $uri/ /index.php?q=$uri&$args;
so now I can use pretty permalinks in WordPress.
But if I try to add a new .conf file with your code it will not work.
Any suggestions?
Thanks.
Sir, I am trying to set up a flask restful API with nginx and gunicorn as directed in this blog https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-16-04 , but I don’t know what to put in the place of server_domain_or_IP in front of server name in the /etc/nginx/sites-available/myproject file. I don’t have my own paid website but want to host it locally.
Wonderful resourceful information. I like what i’ve
actually
acquired here. You make your site articles easy and enjoyable to grasp.
a
I can not wait to read more from you. Bookmarked!
thanks for great explanations! works great on my proxy
Pretty portion of content. I simply stumbled upon your web site
and in accession capital to say that I get actually loved account your blog posts.
Anyway I’ll be subscribing on your feeds or even I achievement you access consistently fast.
Thank you so much for this post. I had trouble with using server_name _; and redirecting to server_name in my 301, which redirected to “_”.
by the way: Your site locks out Tor users because of Cloudflare. You can disable the captcha for them, or set Tor users to be verified via the Cloudflare javascript check
I would such a guide that doesn’t use custom image / script. I like to understand what I’m doing. Thanks a lot though, I analysed your script and adapted it to my needs.
Thanks. Your post very helped me.
The problem i’m having is that when i type the IP address into the address bar it does not get forwarded to my port 443 and https:// Any thoughts?
I will іmmedіately seize your rss as I cаn’t
in findіng your email subscription hyperlink or e-newsletter service.
Do you’ve any? Please let me recognise in order that I may
just ѕubscribe. Thankѕ.
I have a Mainframe application which is trying to call a HTTPS endpoint which is setup as an enterprise API using APIGEE gateway in my internal network. This request go through a corporate proxy(webproxy.org).
Having said that, Mainframe CICS is not supporting SNI to the end-point and getting certificate errors, So plan is to make a call to in-between reverse proxy server(NGINX/Apache) which will just trigger a plain http request to the proxy server so that it will convert it to the HTTPS endpoint. So that the proxy will take care of handling the SSL certificates & keys. but the problem here is that it is not routing through the corporate proxy and hence i am not getting response on the same. I tried with return 307 but it didn’t work out as mainframes again can’t re-route it to https because of previous problem, any suggestions on how to proceed…. i mean where can i mention webproxy.org to call https://$servername$request_uri…any suggestions are always welcome.
Very usefull, works fine to me.
Hi i have a url in nginx
mailsvr.parco.net.pk/mailsvr/mail
i want to remove the trailing */mailsvr/* in the url, can someone help me out.
Useful information, thanks
Thank you for the information and technical details. Good information for your friends and mine. Wishing everyone benefits
I have followed the similar changes to redirect but I have faced its taking too long to render the application! is there some enhancement I can do to make that work! I am pretty new on nginx!
thank you!
Thanks you bro!