Disclaimer: this is technically related to a school project, but I've talked to my professor and he is also confused by this.
I have a nginx load balancer reverse proxying to several uwsgi + flask apps. The apps are meant to handle very high throughput/load. My response times from uwsgi are pretty good, and the nginx server has low CPU usage and load average, but the overall request time is extremely high.
I've looked into this issue and all the threads I've found say that this is always caused by the client having a slow connection. However, the requests are being made by a script on the same network, and this issue isn't affecting anyone else's setup, so it seems to me that it's a problem with my nginx config. This has me totally stumped though because it seems almost unheard of for nginx to be the bottleneck like this.
To give an idea of the magnitude of the problem, there are three primary request types: add image, search, and add tweet (it's a twitter clone).
For add image, the overall request time is ~20x longer than the upstream response time on average. For search, it's a factor of 3, and add tweet 1.5. My theory for the difference here is that the amount of data being sent back and forth is much larger for add image than either search or add tweet, and larger for search than add tweet.
My nginx.conf is:
user www-data;
pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 30000;
events {
worker_connections 30000;
}
http {
# Settings.
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_body_buffer_size 200K;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# Logging
log_format req_time '$remote_addr - $remote_user [$time_local] '
'REQUEST: $request '
'STATUS: $status '
'BACK_END: $upstream_addr '
'UPSTREAM_TIME: $upstream_response_time s '
'REQ_TIME: $request_time s ';
'CONNECT_TIME: $upstream_connect_time s';
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log req_time;
# GZIP business
gzip on;
gzip_disable "msie6";
# Routing.
upstream media {
server 192.168.1.91:5000;
}
upstream search {
least_conn;
server 192.168.1.84:5000;
server 192.168.1.134:5000;
}
upstream uwsgi_app {
least_conn;
server 192.168.1.85:5000;
server 192.168.1.89:5000;
server 192.168.1.82:5000;
server 192.168.1.125:5000;
server 192.168.1.86:5000;
server 192.168.1.122:5000;
server 192.168.1.128:5000;
server 192.168.1.131:5000;
server 192.168.1.137:5000;
}
server {
listen 80;
server_name localhost;
location /addmedia {
include uwsgi_params;
uwsgi_read_timeout 5s;
proxy_read_timeout 5s;
uwsgi_pass media;
}
location /media {
include uwsgi_params;
uwsgi_read_timeout 5s;
proxy_read_timeout 5s;
uwsgi_pass media;
}
location /search {
include uwsgi_params;
uwsgi_read_timeout 5s;
proxy_read_timeout 5s;
uwsgi_pass search;
}
location /time-search {
rewrite /time-search(.*) /times break;
include uwsgi_params;
uwsgi_pass search;
}
location /item {
include uwsgi_params;
uwsgi_read_timeout 5s;
proxy_read_timeout 5s;
if ($request_method = DELETE) {
uwsgi_pass media;
}
if ($request_method = GET) {
uwsgi_pass uwsgi_app;
}
if ($request_method = POST) {
uwsgi_pass uwsgi_app;
}
}
location / {
include uwsgi_params;
uwsgi_read_timeout 5s;
proxy_read_timeout 5s;
uwsgi_pass uwsgi_app;
}
}
}
And my uwsgi ini is:
[uwsgi]
chdir = /home/ubuntu/app/
module = app
callable = app
master = true
processes = 25
socket = 0.0.0.0:5000
socket-timeout = 5
die-on-term = true
home = /home/ubuntu/app/venv/
uid = 1000
buffer-size=65535
single-interpreter = true
Any insights as to the cause of this problem would be greatly appreciated.
So, I think I figured this out. From reading the nginx docs (https://www.nginx.com/blog/using-nginx-logging-for-application-performance-monitoring/) it seems that there are three metrics to pay attention to: upstream_response_time, request_time, and upstream_connect_time. I was focused on the difference between upstream_response_time and request_time.
However, upstream_response_time is the time between the upstream accepting the request and returning a response. It doesn't include upstream_connect time, or the time it takes to establish a connection to upstream server. And in the context of uwsgi, this is very important, because if there isn't a worker available to accept a request, the request will get put on a backlog. I think the time a request waits on the backlog might count as upstream_connect_time, not upstream_response_time in nginx, because uwsgi hasn't read any of the bytes yet.
Unfortunately, I can't be 100% certain, because I never got a "slow" run where I was logging upstream_connect_time. But the only things I changed that improved my score were just "make the uwsgi faster" changes (devote more cores to searching, increase replication factor in the DB to make searches faster)... So yeah, turns out the answer was just to increase throughput for the apps.
Related
I am trying to configure cgit with nginx through uwsgi. I managed to get the main page working on example.com/ and added my repos but when I try to access a repo in example.com/somerepo I get a 502 error.
I know cgit is working fine because I can run cgit.cgi with and without the QUERY_STRING="url=somerepo"environmental variable and it generates the correct html for the main page and the somerepo page respectively.
I have been trying to debug the issue using the nginx error logs with debug level, strace and gdb on both nginx and cgit.cgi and the output from uwsgi, this is what I've found so far:
When I click on a somerepo link on cgit's main page uwsgi makes a GET request to /somerepo and nginx tries to open a directory in /htdocs/somerepo which it can't find because it doesn't exist. (I suppose cgit.cgi should generate this on the fly). I know this from strace stat("/usr/share/webapps/cgit/1.2.1/htdocs/olisrepo/", 0x7ffdf4c817c0) = -1 ENOENT (No such file or directory)
When I click on a somerepo link I get read(8, 0x561749c8afa0, 65536) = -1 EAGAIN (Resource temporarily unavailable) from cgit.cgi's strace.
When I try to visit a invalid url like somerepotypo it correctly generates a 404 page saying 'no repositories found'.
These are my configuration files:
/etc/nginx/nginx.conf
user nginx nginx;
worker_processes 1;
error_log /var/log/nginx/error_log debug;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
client_header_timeout 10m;
client_body_timeout 10m;
send_timeout 10m;
connection_pool_size 256;
client_header_buffer_size 1k;
large_client_header_buffers 4 2k;
request_pool_size 4k;
gzip off;
output_buffers 1 32k;
postpone_output 1460;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 75 20;
ignore_invalid_headers on;
# Cgit
server {
listen 80;
server_name example.com;
root /usr/share/webapps/cgit/1.2.1/htdocs;
access_log /var/log/nginx/access_log main;
error_log /var/log/nginx/error_log debug;
location ~* ^.+(cgit.(css|png)|favicon.ico|robots.txt) {
root /usr/share/webapps/cgit/1.2.1/htdocs;
expires 30d;
}
location / {
try_files $uri #cgit;
}
location #cgit {
include uwsgi_params;
uwsgi_modifier1 9;
uwsgi_pass unix:/run/uwsgi/cgit.sock;
}
}
}
cgit.ini (I load this using uwsgi --ini /etc/uwsgi.d/cgit.ini)
[uwsgi]
master = true
plugins = cgi
chmod-socket = 666
socket = /run/uwsgi/%n.sock
uid = nginx
gid = nginx
processes = 1
threads = 1
cgi = /usr/share/webapps/cgit/1.2.1/hostroot/cgi-bin/cgit.cgi
/etc/cgitrc
css=/cgit.css
logo=/cgit.png
mimetype-file=/etc/mime.types
virtual-root=/
remove-suffix=1
enable-git-config=1
scan-path=/usr/local/cgitrepos
Can you help me fix this? Thanks in advance
As my tcpclient is behind nginx.
My idea is to have backend tcpclient, authenticate the external server.
Finally, i had this configuration:
#secured TCP part
stream {
log_format main '$remote_addr - - [$time_local] protocol $status $bytes_sent $bytes_received $session_time "$upstream_addr"';
server {
listen 127.0.0.1:10515 ;
proxy_pass REALIP:REALPORT;
proxy_ssl on;
#server side authentication (client verifying server's certificate)
proxy_ssl_protocols TLSv1.2;
proxy_ssl_certificate /f0/client.crt;
proxy_ssl_certificate_key /f0/client.key;
access_log /var/log/nginx.tcp1.access.log main;
error_log /var/log/nginx.tcp1.error.log debug;
#The trusted CA certificates in the file named by the proxy_ssl_trusted_certificate directive are used to verify the certificate on the ups
tream
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /f0/client_ca.crt;
proxy_ssl_verify_depth 1;
proxy_ssl_session_reuse on;
proxy_ssl_name localhost;
ssl_session_timeout 4h;
ssl_handshake_timeout 30s;
ssl_prefer_server_ciphers on;
#proxy_ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-A
ES256-SHA384:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384";
proxy_ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:EECDH+AESGCM:EDH
+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-R
SA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:D
HE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA";
#ssl_ecdh_curve prime256v1:secp384r1;
#ssl_session_tickets off;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
Can you please authenticate the above configuration and pls let me know, if I am doing right way of verifying the peer.
If yes, my other question:
Is there a way, we can give peer certificate(public key) rather than its CA and verify.
Pls clarify
Thanks,
The question looks obvious as it doesn't mention any specific aspects (apart from TLS).
Please visit the following to get a good start:
https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/
https://www.nginx.com/blog/tcp-load-balancing-udp-load-balancing-nginx-tips-tricks/
Quoting simple example configuration from the above sources:
stream {
server {
listen 12345;
#TCP traffic will be forwarded to the "stream_backend" upstream group
proxy_pass stream_backend;
}
server {
listen 12346;
#TCP traffic will be forwarded to the specified server
proxy_pass backend.example.com:12346;
}
}
I have a supervisord server running on localhost:9001.
I am trying to serve it at localhost/supervisord.
The nginx config is like this:
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /tmp/nginx.pid;
#daemon off;
events {
worker_connections 1024;
}
http {
# MIME / Charset
default_type application/octet-stream;
charset utf-8;
# Logging
access_log /var/log/nginx/access.log;
# Other params
server_tokens off;
tcp_nopush on;
tcp_nodelay off;
sendfile on;
upstream supervisord {
server localhost:9001;
}
server {
listen 80;
client_max_body_size 4G;
keepalive_timeout 5;
location ^~ /stylesheets {
alias /Users/ocervell/.virtualenvs/ndc-v3.3/lib/python2.7/site-packages/supervisor/ui/stylesheets;
access_log off;
}
location ^~ /images {
alias /Users/ocervell/.virtualenvs/ndc-v3.3/lib/python2.7/site-packages/supervisor/ui/images;
access_log off;
}
location /supervisord {
# Set client IP / Proxy IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
# Set host header
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://supervisord/;
}
}
}
Before adding ^~ /images and ^~ /stylesheets locations the page was returning a 502 Bad Gateway.
With the above config I am able to access localhost/supervisord but the CSS is missing on the page.
I see the css / images are loaded correctly in the browser:
But I see an error message in the browser console and it seems to be the culprit:
The mimetype in the browser for localhost/stylesheets/supervisor.css it shows as octet-stream instead of text/css.
The mimetype in the browser for localhost:9001/stylesheets/supervisor.css it shows as the correct text/css.
How can I fix this error ?
I thought about dynamically rewriting the mimetype for static files, but I am not an expert in nginx and have no idea how to do that from nginx config.
It's really interesting such an obvious function like putting web interfaces behind transparent reverse proxy is not that straightforward to configure as it should be.
Anyway this is what I do to get reverse proxy working with applications where root can't be modified, like supervisor:
location /supervisor {
proxy_pass http://127.0.0.1:9001/;
}
location / {
if ($http_referer ~ "^.*/supervisor"){
return 301 /supervisor/$request_uri;
}
}
Application-side requests will hit the main end-point but then NginX will re-direct them to the /supervisor EP
This works in most cases but not always.The following supervisor's web functions will fail:
getting action confirmation - you can start / stop services but the result page will fail to load; just go to the /supervisor EP to check the result
live tail does not work; however there is a log display with manual refresh which works under the link with the program name.
Anyway this partial support is good engough for me and you may find it also useful.
I was able to get it working simply with this:
upstream supervisor {
server 127.0.0.1:9001;
}
server {
# ...
location /supervisor/ {
proxy_pass http://supervisor/;
}
}
Even worked in the browser with and without ending slash in url (ie both http://example.com/supervisor and http://example.com/supervisor/ worked).
This was a must for me!
I use the following code to detect if users are behind proxy/vpn:
function checkUser()
{
$proxy = null;
$check = null;
$proxy = ($_SERVER['HTTP_ACCEPT_ENCODING'] != 'gzip, deflate') ? true : false;
if(empty($_SERVER['HTTP_CONNECTION']) || strtolower($_SERVER['HTTP_CONNECTION']) != 'keep-alive' || $_SERVER['HTTP_CACHE_CONTROL'] != 'max-age=0')
{
$check = ($proxy === true) ? 'proxy' : 'vpn';
}
return $check;
}
$connection = checkUser();
switch($connection)
{
case 'proxy': $var = 'It seems you are behind Proxy.'; break;
case 'vpn': $var = 'It seems you are using VPN.'; break;
default: $var = 'No Proxy or VPN detected.'; break;
}
echo $var;
However it does work just fine on an older server I have, but on the new one it just doesn't. The new server is using Reverse Proxy Server (nginx). Can someone tell me if it has something to do with nginx and what I should adjust at the config. Thanks!
--- EDIT: ---
#user nginx;
worker_processes 4;
worker_rlimit_nofile 950000;
#error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log notice;
#error_log /var/log/nginx/error.log info;
#pid /var/run/nginx.pid;
events {
worker_connections 45000;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$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;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 5;
#tcp_nodelay on;
#gzip on;
#gzip_disable "MSIE [1-6]\.(?!.*SV1)";
server_tokens off;
include /etc/nginx/conf.d/*.conf;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
Your code seems to be based on the assumption that a proxy would disable the use of gzip/deflate and/or keep-alive sessions. This is not an accurate assumption. It is easier to implement a proxy if these features are turned off, but per the spec there is nothing that precludes a proxy working correctly with these features on.
So yes, nginx is probably just a better proxy and so the assumptions made by the author of that code above are now wrong.
The right way to check for an HTTP proxy is to look for the presence of an X-Forwarded-For header. Something like this would suffice:
function isProxied() {
$headers = array_change_key_case(apache_request_headers());
return isset($headers["x-forwarded-for"]);
}
Technically, a proxy can be implemented without advertising it's presense (without adding an X-Forwarded-For header) - and some have an option to do this, in which case you're not really going to be able to detect this. But most propxies will cooperate with you.
Note that if you are using a proxy in your own server stack (i.e. if you are running Varnish, Nginx or something else in front of Apache) then that could also adding an X-Forwarded-For header, so everything would appear to be proxied (based on this, it looks like nginx uses "X-Real-IP" by default, so you likely don't need to worry about this). If this is the case, either turn off the that option in Varnish/Nginx/whatever, or parse the X-Forwarded-For header to see if there are two IPs there instead of one.
Regarding VPN connections, I do not think you are going to find a reliable way to detect if the user is on VPN just from an incoming HTTP connection. Although you might consider checking his origin IP to see if he is coming from a known TOR address or something of the sort. Depending on how much you care about that.
Sometimes I get an issue with error 502 when httpd service is down.
But only in 1 minute the website come back.
I need to custom the 502 message to ask user to wait for 1 minute then refresh page, or embed JavaScript or meta refresh tag to auto refresh page after 1 minute.
Page's URL must be the same to make refresh effect
Notice that I know about custom error page redirect eg location = /502.html, but that type of custom error page will redirect user to other page, if they will refresh page they will got error page again.
Any idea will be very helpful.
EDIT UPDATE for more detail 10/06/2012.
My nginx config:
user nobody;
# no need for more workers in the proxy mode
worker_processes 24;
error_log /var/log/nginx/error.log crit;
#worker_rlimit_nofile 20480;
events {
worker_connections 109024; # increase for busier servers
use epoll; # you should use epoll here for Linux kernels 2.6.x
}
http {
server_name_in_redirect off;
server_names_hash_max_size 2048;
server_names_hash_bucket_size 256;
include mime.types;
default_type application/octet-stream;
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 20;
ignore_invalid_headers on;
client_header_timeout 50m;
client_body_timeout 50m;
send_timeout 20m;
reset_timedout_connection on;
connection_pool_size 2048;
client_header_buffer_size 256k;
large_client_header_buffers 4 256k;
client_max_body_size 20M;
client_body_buffer_size 300k;
request_pool_size 32k;
output_buffers 14 32k;
postpone_output 1460;
proxy_temp_path /tmp/nginx_proxy/;
proxy_cache_path /dev/shm/nginx levels=1:2 keys_zone=wwwcache:45m inactive=5m max_size=1000m;
client_body_in_file_only off;
access_log off;
open_log_file_cache off;
#log_format bytes_log "$msec $bytes_sent .";
include "/etc/nginx/vhosts/*";
}
and vhost config:
server {
# error_log /var/log/nginx/vhost-error_log warn;
listen 123.30.137.66:80;
server_name xaluan.net mtvvui.com www.daiduong.com.au www.xaluan.net xaluan.com www.xaluan.com www.daiduongrestaurant.net veryzoo.com www.mtvvui.com www.xaluan.org www.veryzoo.com daiduongrestaurant.net xaluan.org daiduong.com.au;
# access_log /usr/local/apache/domlogs/xaluan.net combined;
root /home/xaluano/public_html;
location / {
if ($http_cache_control ~ "max-age=0") {
set $bypass 1;
}
location
~.*\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$
{
#root /home/xaluano/public_html;
#proxy_cache wwwcache;
#proxy_cache_valid 200 15m;
#proxy_cache_bypass $bypass;
expires 1d;
#try_files $uri #backend;
proxy_pass http://123.30.137.66:8081;
}
error_page 405 = #backend;
add_header X-Cache "HIT from Backend";
#proxy_set_header Server "Caching-Proxy";
#add_header X-Cache-Vinahost "HIT from Backend";
proxy_pass http://123.30.137.66:8081;
include proxy.inc;
}
location #backend {
internal;
proxy_pass http://123.30.137.66:8081;
include proxy.inc;
}
location ~ .*\.(php|jsp|cgi|pl|py)?$ {
#proxy_cache wwwcache;
#proxy_cache_valid 200 15m;
proxy_pass http://123.30.137.66:8081;
include proxy.inc;
}
location ~ /\.ht {
deny all;
}
}
== the case test..
If Apache httpd service stops: #service httpd stop
Then open in browser this link:
http://www.xaluan.com/modules.php?name=News&file=article&sid=123456
You will see the 502 error with the same URL on browser address.
== Custom error page
I need the config which will help when Apache fail, will show the custom message telling user to wait for 1 minute for service back, then refresh current page with same URL (refresh I can do easy by JavaScript), Nginx does not change URL so JavaScript can work out.
I found an answer that works for me.
In the vhost config file, I put right at the end of the server block, before closing brace:
error_page 502 /502.html;
location = /502.html {
root /home/xaluano/public_html;
}
Of course I also need to create a file 502.html at my domain root, with the meta-tag refresh, and java-script auto refresh.
The content of html page is:
<head>
<meta http-equiv="refresh" content="40" />
</head>
<body>
<script language="JavaScript" type="text/javascript">
/*<![CDATA[*/
var TimerVal = 40;
var TimerSPan = document.getElementById("CDTimer");
function CountDown(){
setTimeout( "CountDown()", 1000 );
TimerSPan.innerHTML=TimerVal;
TimerVal=TimerVal-1;
if (TimerVal<0) { TimerVal=0;
location.reload(true);
// window.location.href = "http://www.xaluan.com";
} //improvement by vivalibre, tq
}
CountDown();
/*]]>*/ </script>
</body>
http://nginx.org/r/error_page
Note that error_page 502 /502.html; performs internal redirect. It does not change the URL in browser address bar.