Nginx – why is partial content not being served in nginx (mp4)

nginxvideo

i am running nginx v.1.9.3 here to serve my site and also video files for streaming. i am seeing that the mp4 files are always served with a code of 200 and that partial content requests are ignored. sometimes videos are able to be 'seeked' in browsers and sometimes not. i have used various file formats and the results vary from ok-ish to fatally bad.

i used curl -I to view the return data from nginx and saw that there was no mention of the server accepting byte-ranges and when i manually request parts of files using curl i see that in all cases the return code is 200.

i have tested with and without the mp4 module activated in the nginx config file build and neither made a significant advantage over the other.

an example file can be viewed here (this is mp4 h264 format as i heard this was better for modern browsers): https://www.ureka.org/file/play/18212/censored%20on%20google%202-h264.mp4

this is my site's config file:

server {
    server_name www.mysite.org;
    listen 443 ssl spdy default_server;
    ssl_certificate_key /mysitekey.key;
    ssl_certificate /mysitekey.crt;
    keepalive_timeout     300;
    spdy_keepalive_timeout 300;

    index index.php index.html index.htm;

    # https only, mode - Remember this setting for 365 days
    add_header Strict-Transport-Security max-age=31536000;

    access_log /mysite.log;
    error_log /mysite.log;
    root /mysite/path;

    client_header_buffer_size 1k;
    fastcgi_index index.php;
    client_max_body_size  2G;
    client_body_buffer_size 1K;
    proxy_read_timeout 600;

    error_page   500  /500.html;
    error_page   502  /502.html; #bad gateway
    error_page   503  /503.html;
    error_page   504  /504.html; # gateway timeout

    location ~ (^\.|/\.) {
        return 403;
    }

    set $cache_uri $request_uri;

    # POST requests and urls with a query string should always go to PHP
    if ($request_method = POST) {
        set $cache_uri 'null cache';
    }
    if ($query_string != "") {
        set $cache_uri 'null cache';
    }

    # Don't cache uris containing the following segments
    if ($request_uri ~* "(/admin/|/xml-rpc_handler.php|/(cron|sign-in|joyn|messages).php|/feed/|index.php|sitemap.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $cache_uri 'null cache';
    }

    location /cache {
        rewrite ^/cache\/(.*)$ /engine/handlers/cache_handler.php?request=$1&$query_string;
    }

    location /export {
        rewrite ^/export\/([A-Za-z]+)\/([0-9]+)\/?$ /engine/handlers/export_handler.php?view=$1&guid=$2;
        rewrite ^/export\/([A-Za-z]+)\/([0-9]+)\/([A-Za-z]+)\/([A-Za-z0-9\_]+)\/$ /engine/handlers/export_handler.php?view=$1&guid=$2&type=$3&idname=$4;
    }

    location = /rewrite.php {
        rewrite ^(.*)$ /install.php;
    }

    location / {
        try_files $uri $uri/ /index.php?__elgg_uri=$uri&$query_string;
    }

    location ~ /\.well-known
    {
        access_log off;
        log_not_found off; 
    }

    location ~ /\. 
    {
        access_log off;
        log_not_found off; 
        deny all;
    }   

    # Prevent clients from accessing hidden files (starting with a dot)
    # This is particularly important if you store .htpasswd files in the site hierarchy
    location ~* (?:^|/)\. {
        deny all;
    }

    # Prevent clients from accessing to backup/config/source files
    location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
        deny all;
    }

    location ~ \.php$ 
    {
    # Fastcgi cache
    set $skip_cache 1;
    if ($cache_uri != "null cache") {
        add_header X-Cache-Debug "$cache_uri $cookie_nocache $arg_nocache$arg_comment $http_pragma $http_authorization";
        set $skip_cache 0;
    }
    fastcgi_cache_bypass $skip_cache;
    fastcgi_cache microcache;
    fastcgi_cache_key $scheme$host$request_uri$request_method;
    fastcgi_cache_valid any 8m;
    fastcgi_cache_bypass $http_pragma;
    fastcgi_cache_use_stale updating error timeout invalid_header http_500;
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/socket/php5-fpm.sock;
    fastcgi_index  index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_connect_timeout 60;
    fastcgi_send_timeout 180;
    fastcgi_read_timeout 600;
    fastcgi_buffers 256 4k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_intercept_errors on;
    fastcgi_param  QUERY_STRING     $query_string;
    fastcgi_param  REQUEST_METHOD   $request_method;
    fastcgi_param  CONTENT_TYPE     $content_type;
    fastcgi_param  CONTENT_LENGTH   $content_length;
}

## cache headers 

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
  expires -1;
  add_header Cache-Control "private";
  add_header Pragma "private";
}

# Feed
location ~* \.(?:rss|atom)$ {
  expires 1h;
  add_header Cache-Control "public";
}

location ~ \.flv$ {
    flv;
}   
}

and here is the main nginx.conf file:

user nginx;
worker_processes  4;
worker_priority -5;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

# Maximum open file descriptors per process;
# should be > worker_connections.
worker_rlimit_nofile 40000;

events 
{
    worker_connections  8096;
    use epoll;
    multi_accept on;
}

http 
{   
    # Define the MIME types for files.
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    underscores_in_headers on;

    # do not allow content to be framed
    add_header X-Frame-Options DENY;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';
    # caching using 50MB of RAM and 1000MB of disk space
    fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=microcache:50m max_size=1000m inactive=600m;

    # Force the latest IE version
    # Use ChromeFrame if it's installed for a better experience for the poor IE folk
    add_header "X-UA-Compatible" "IE=Edge"; 

 # cache file DESCRIPTORS

    open_file_cache max=2000 inactive=20s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 3;
    open_file_cache_errors on;
    keepalive_requests 100000;
    server_tokens off; # hides nginx version number

      # Speed up file transfers by using sendfile() to copy directly
      # between descriptors rather than using read()/write().
  sendfile        on;

  # Tell Nginx not to send out partial frames; this increases throughput
  # since TCP frames are filled up before being sent out. (adds TCP_CORK)
  tcp_nopush      on;

    ## Global SSL options
  ssl_session_cache builtin:1000 shared:SSL:10m; # a 1mb cache can hold about 4000 sessions, so we can hold 40000 sessions
  ssl_session_timeout  24h;

  # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /path/dhparam.pem;

    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:HIGH:DH+AES:ECDH+3DES:DH+3DES:!eNULL:!NULL:!aNULL:!EDH:!MD5:!DSS; # previously produced A grade test result

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    #ssl_buffer_size 4k;
    ssl_buffer_size 1400; # 1400 bytes to fit in one MTU
    ssl_session_tickets off;

    # enable SPDY header compression
    spdy_headers_comp 6;

    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them

    ssl_stapling on; # Requires nginx >= 1.3.7
    ssl_stapling_verify on; # Requires nginx => 1.3.7

    resolver 156.154.70.1 156.154.71.1 valid=300s;
    resolver_timeout 5s;

  # Compression

  # Enable Gzip compressed.
  gzip on;

  # Enable compression both for HTTP/1.0 and HTTP/1.1 (required for CloudFront).
  gzip_http_version  1.0;

  # Compression level (1-9).
  # 5 is a perfect compromise between size and cpu usage, offering about
  # 75% reduction for most ascii files (almost identical to level 9).
  gzip_comp_level    6;

  # Don't compress anything that's already small and unlikely to shrink much
  # if at all (the default is 20 bytes, which is bad as that usually leads to
  # larger files after gzipping).
  gzip_min_length    10000;

  # Compress data even for clients that are connecting to us via proxies,
  # identified by the "Via" header (required for CloudFront).
  gzip_proxied       any;

  # Tell proxies to cache both the gzipped and regular version of a resource
  # whenever the client's Accept-Encoding capabilities header varies;
  # Avoids the issue where a non-gzip capable client (which is extremely rare
  # today) would display gibberish if their proxy gave them the gzipped version.
  gzip_vary          on;

  # Compress all output labeled with one of the following MIME-types.
  gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/rss+xml
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/svg+xml
    image/x-icon
    text/css
    text/plain
    text/javascript
    application/font-woff
    text/x-component;
  # text/html is always compressed by HttpGzipModule

    gzip_static       on;
    gzip_buffers 16 8k;
    gzip_disable "MSIE [1-6]\.";

    ## Start: Timeouts ##
    client_body_timeout   30;
    client_header_timeout 30;
    keepalive_timeout     100;
    send_timeout          100;  
    ## End: Timeouts ##

    ## Reset lingering timed out connections. Deflect DDoS.
    reset_timedout_connection on;
    ### Directive describes the zone, in which the session states are stored i.e. store in slimits. ###
     ### 1m can handle 32000 sessions with 32 bytes/session, set to 5m x 32000 session ###
   limit_conn_zone $binary_remote_addr zone=perip:10m;
   limit_req_zone $binary_remote_addr zone=periprate:10m rate=1000r/s;
   limit_conn_zone $server_name zone=perserver:10m;
    #  limit_rate 1280k;
    ### Control maximum number of simultaneous connections for one session i.e. ###
    ### restricts the amount of connections from a single ip address ###
    limit_conn perip 200;
    ### limit connections for the total server
    limit_conn perserver 15000;


    ## Enable clickjacking protection in modern browsers. Available in
    ## IE8 also. See
    ## https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header
    add_header X-Frame-Options SAMEORIGIN;

    ## Include the cache map to decide when or not when to cache.
    include map_cache_piwik.conf;

    ## Include the php-fpm status allowed hosts configuration block.
    ## Uncomment to enable if you're running php-fpm.
    include php_fpm_status_allowed_hosts.conf;

    include /etc/nginx/sites-enabled/*;
}

if anyone knows why this is failing i'd love for you to share here.. thanks

Best Answer

the cause of the problem was the lack of range handling in the PHP page i am using for streaming the files. i forgot that that is a requirement of the process! i have added the videostream class (http://codesamplez.com/programming/php-html5-video-streaming-tutorial) to the page and so far the streaming is working well in my tests :)