It is hard to say with 100% certainty but I think the problem is your use of query_posts. Here is what I believe is happening:
- The Main query runs with the default parameters and loads your page
- You clobber the main query with
query_posts - You generate pagination based on the clobbered main query
- You click a link
- The unclobbered main query tries to sort out what you want (remember, your
query_postscan’t run until WordPress loads the template) - And fails and you get a 404
Just don’t use query_posts. If you ever think you have found a legitimate place to use query_posts you are almost certainly wrong. The Core is the only thing that should be running that function.
If you must alter the main query use a filter on pre_get_posts.
function alter_query_wpse_99042($qry) {
$qry->set('posts_per_page',6);
$qry->set('post_type','portfolio');
}
add_action('pre_get_posts','alter_query_wpse_99042');
However, based on your post type registration settings it looks like WordPress should be building the archive for you. I am not sure why you need to be constructing a query at all, except maybe to change posts_per_page. You can customize the generated archive by making a file named archive-portfolio.php and saving it to the theme directory. single-portfolio.php does the same with single post-type displays. Those files are loaded automatically.