posts_nav_link on single.php

The short answer:

You can’t do what you’re trying to do, using the function you’re using.

The long answer:

The posts_nav_link() template tag does not return any output on single blog posts. It calls get_posts_nav_link(), which is defined in source as follows:

function get_posts_nav_link( $args = array() ) {
        global $wp_query;

        $return = '';

        if ( !is_singular() ) {
                $defaults = array(
                        'sep' => ' — ',
                        'prelabel' => __('« Previous Page'),
                        'nxtlabel' => __('Next Page »'),
                );
                $args = wp_parse_args( $args, $defaults );

                $max_num_pages = $wp_query->max_num_pages;
                $paged = get_query_var('paged');

                //only have sep if there's both prev and next results
                if ($paged < 2 || $paged >= $max_num_pages) {
                        $args['sep'] = '';
                }

                if ( $max_num_pages > 1 ) {
                        $return = get_previous_posts_link($args['prelabel']);
                        $return .= preg_replace('/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $args['sep']);
                        $return .= get_next_posts_link($args['nxtlabel']);
                }
        }
        return $return;

}

The important conditional:

if ( !is_singular() )

The is_singular() conditional returns true when the current page is a single blog post, static page, or attachment page. Since you’re on a single blog post, the function simply returns an empty string.