Sorry for the edit history but this issue was really unclear to me and it was difficult to locate the exact problem.
I have a .Net-Core web application that runs behind a Nginx and the X-Forwarded-Proto always passes http
instead of https
.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//first middlewear
app.UseForwardedHeaders();
//and the rest
}
Nginx conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:5001/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Nginx.conf
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
#cloudflare real ip
#https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs-Logging-visitor-IP-addresses-with-mod-cloudflare-#12345681
set_real_ip_from 173.245.48.0/20;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main '"$scheme" $remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
access.log record
"http" 185.108.83.156 - - [03/Oct/2019:19:59:33 +0300] "GET /auth/signin/Facebook?returnUrl=%2F HTTP/1.1" 302 0 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36" "156"
as you can see the $scheme that I log is always HTTP.
A solution that solves the issue is to enforce Scheme to be HTTPS like so:
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next();
});
from https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2#forwarded-headers-middleware-options
But with this solution I don't pass the headers and loses some information.
So does anyone have any solution for this case?
Your Startup.cs file is fine but you didn't configure ssl for Kesterl.
You need to configure a X.509 ssl certificate for your .net core application
From https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.2#when-to-use-kestrel-with-a-reverse-proxy-1
Only the reverse proxy server requires an X.509 certificate, and that
server can communicate with the app's servers on the internal network
using plain HTTP
So in your case you have 7 main steps to do:
1) Get a SSL certificate from CloudFlare ORIGIN CA
2) Install the ssl certificate in your server
3) Create a PFX file so your Kesterl Server can read it.
4) Configure Kesterl https end points.
5) Modify your Nginx server to listen to port 443 and then redirect to Kestrel listening ports
6) Modify service files accordingly your configuration
7) Change Cloudflare to run Strict mode
Step 1 - Get ssl certificate from cloudflare:
Go to your dashboard at Cloudflare and choose SSL/TLS --> Origin Server -->Create Certificate.
Follow the instructions and you will have 2 files at the end of the process:
example.com.key
example.com.pem
Step 2 - Place the files in your server at etc/ssl
folder
Step 3 - Create PFX file so Kesterl can use the certificate:
run this at your server:
openssl pkcs12 -export -out example.pfx -inkey example.com.key -in example.com.pem
This will generate the example.pfx, place it in the same folder.
Step 4 - Configure Kesterl https end points.
From step 3 you should have got a password that you need to use at this step.
At your appsetting.json
place this lines:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Kestrel": {
"Endpoints": {
"HTTPS": {
"Url": "https://localhost:5001",
"Certificate": {
"Path": "/etc/ssl/example.pfx",
"Password": "**********"
}
}
}
}
Step 5 - Configure your Nginx to listen on port 443 like so:
server {
#listen 80;
listen *:443 ssl;
ssl_certificate /etc/ssl/example.pem;
ssl_certificate_key /etc/ssl/example.key;
server_name example.com
location / {
proxy_pass https://localhost:5001/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Step 6 - configure your service file like so:
[Unit]
Description=example
[Service]
WorkingDirectory=/var/www/example
ExecStart=/usr/bin/dotnet /var/www/example/exampleSite.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Staging
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=ASPNETCORE_HTTPS_PORT=5001
Environment=ASPNETCORE_URLS=https://localhost:5001
[Install]
WantedBy=multi-user.target
Step 7 - Change Cloudflare at SSL/TLS to use Strict mode
Restart your application + Nginx and your server should now work like this:
Request --> Cloudflare(HTTPS) --> Nginx(HTTPS)-->Example.com
Generally, the Headers of HTTP doesn't accept non-ASCII characters. Urls must be properly encoded when generated, the Redirect methods don't do it for you.
A quick fix could be to use the WebUtility.UrlEncode
using System.Net;
// ...
var encodedLocationName = WebUtility.UrlEncode(locationName);
return Redirect("~/locations/" + encodedLocationName);
This looks to be an issue with the way your NGinx file and Cloudflare is setup. From the config, you setup is
HTTPS->Cloudflare->HTTP->Your Server
This is why Scheme is always "http" in your nginx log. As Cloudflare is passing the request to your server over HTTP. You should have TLS setup between Cloudflare and you, which would enable port 443 on your nginx config, which would set the scheme as HTTPS. Forcing dotnet to beleive it was handled over HTTPS is not valid as Cloudflare is sending your clients data unencrypted over the public internet.
https://support.cloudflare.com/hc/en-us/articles/115000479507-Managing-Cloudflare-Origin-CA-certificates
Cloudflare's documentation lists how to set this up with NGinx.
You're only running nginx on port 80 and without ssl, so, the $scheme
variable will always be http
. See http://nginx.org/r/$scheme.
If you care about https, it's probably a good idea to secure the connection between Cloudflare and your backend with HTTPS, too; this way, your $scheme
variable will be populated correctly. Otherwise, you could also hardcode https
in place of $scheme
, but then it defeats the purpose of having a conditional test and a redirect on your real and final .Net backend.
Please set appropriate character set in your nginx file.
Here is the documentation
http://nginx.org/en/docs/http/ngx_http_charset_module.html#charset_types