Recently while configuring a production server, I encountered a bizarre behavior where HTTP requests to my Nginx server resulted in downloading a 57-byte binary file named "download" instead of performing the expected HTTPS redirect. Here's how I diagnosed and fixed the issue.
# Initial symptom when making HTTP request:
$ curl -v http://example.com
* Rebuilt URL to: http://example.com/
* Connected to example.com (192.0.2.1) port 80
> GET / HTTP/1.1
> Host: example.com
>
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Content-Type: application/octet-stream
< Content-Length: 57
<
▒▒
My original configuration had three server blocks:
# HTTP to HTTPS redirect (problematic)
server {
server_name www.example.com example.com;
listen 80;
return 301 https://example.com$request_uri;
}
# WWW to non-WWW redirect (HTTPS)
server {
server_name www.example.com;
listen 443 ssl;
return 301 https://example.com$request_uri;
}
# Main HTTPS server
server {
server_name example.com;
listen 443 ssl;
root /var/www/example.com;
# ... other configurations ...
}
The key findings from troubleshooting:
- The HTTP server block wasn't properly terminating
- Nginx was falling through to default behavior
- No default_server was specified in listen directives
- Binary output suggests header/body corruption
After several tests, here's the corrected configuration:
# Corrected HTTP server block
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name www.example.com example.com;
# Clear any existing Content-Type
add_header Content-Type "";
# Force empty response body
return 301 https://$host$request_uri;
}
# HTTPS configurations remain unchanged
To confirm the fix worked:
$ curl -I http://example.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Location: https://example.com/
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
For more complex setups, consider these additional measures:
# For load balancers or proxies
server {
listen 80;
server_name _;
# Special handling for health checks
location = /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
# Redirect everything else
location / {
return 301 https://$host$request_uri;
}
}
Key takeaways from this debugging session:
- Always specify
default_server
for catch-all behavior - Explicitly set Content-Type for redirect responses
- Use
$host
variable instead of hardcoding domains - Test with both
curl -v
and browser requests
Recently, I encountered a strange issue with my Nginx configuration where HTTP requests to my domain were downloading a 57-byte file named "download" (no extension) containing gibberish text, while HTTPS worked perfectly. Here's what my configuration looked like:
server {
server_name www.domain.com domain.com;
listen 80;
return 301 https://domain.com$request_uri;
}
When running curl -v http://domain.com
, I noticed the response was incomplete:
Connected to domain.com (175.*:*:*) port 80 (#0)
> GET / HTTP/1.1 > User-Agent: curl/7.38.0
> Host: domain.com > Accept: */*
> * Connection #0 to host domain.com left intact
▒▒
The issue stemmed from an incomplete HTTP to HTTPS redirect configuration. While the redirect was technically working, some clients (particularly browsers) were interpreting the response incorrectly, resulting in the file download behavior.
Here's the corrected configuration that resolved the issue:
server {
server_name www.domain.com domain.com;
listen 80;
# Proper HTTP to HTTPS redirect
return 301 https://$host$request_uri;
}
server {
server_name www.domain.com;
listen 443 ssl;
# SSL configuration here
# Canonical redirect
return 301 https://domain.com$request_uri;
}
server {
server_name domain.com;
listen 443 ssl;
root /usr/share/nginx/domain.com;
# SSL configuration here
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
1. Used $host
instead of hardcoding the domain in the redirect
2. Ensured proper syntax in all server blocks
3. Verified all redirects use the correct HTTP 301 status code
After implementing these changes, running curl -v http://domain.com
now shows the proper redirect:
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Location: https://domain.com/
...
For better security and reliability, consider adding these to your Nginx configuration:
# Force HTTPS for all requests
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Prevent content type sniffing
add_header X-Content-Type-Options "nosniff";
# Add security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";