diff --git a/app/assets/stylesheets/error_pages.css.scss b/app/assets/stylesheets/error_pages.css.scss
new file mode 100644
index 0000000000000000000000000000000000000000..75427f6859b4f337e7483e69756e87760020f865
--- /dev/null
+++ b/app/assets/stylesheets/error_pages.css.scss
@@ -0,0 +1,24 @@
+@import 'mixins';
+
+#big-number {
+  font-family: Roboto-BoldCondensed, Helvetica, Arial, sans-serif;
+  font-size: 250px;
+  line-height: 1em;
+  text-align: center;
+  padding-top: 100px;
+  text-shadow: 0 2px 0 #fff, 0 -1px 0 #999;
+  color: #ddd;
+}
+.transparent {
+  @include opacity(0.8);
+}
+#content {
+  font-family: Roboto, Helvetica, Arial, sans-serif;
+  text-align: center;
+  text-shadow: 0 1px 0 #fff;
+  font-size: 1.25em;
+  line-height: 1.5em;
+  color: #666;
+  position: absolute;
+  left: 0; right: 0;
+}
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 84ab09caf092b191376c2bd42d018117ef05b3bc..acd65d41ce93afe17cd84bd422245ef4d5f76b68 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -6,7 +6,7 @@ require Rails.root.join("app", "presenters", "post_presenter")
 
 class PostsController < ApplicationController
   include PostsHelper
-  
+
   before_filter :authenticate_user!, :except => [:show, :iframe, :oembed, :interactions]
   before_filter :set_format_if_malformed_from_status_net, :only => :show
   before_filter :find_post, :only => [:show, :next, :previous, :interactions]
@@ -18,6 +18,13 @@ class PostsController < ApplicationController
              :json,
              :xml
 
+  rescue_from Diaspora::NonPublic do |exception|
+    respond_to do |format|
+      format.html { render :template=>'errors/not_public', :status=>404 }
+      format.all { render :nothing=>true, :status=>404 }
+    end
+  end
+
   def new
     @feature_flag = FeatureFlagger.new(current_user, current_user.person) #I should be a global before filter so @feature_flag is accessible
     redirect_to "/stream" and return unless @feature_flag.new_publisher?
diff --git a/app/models/post.rb b/app/models/post.rb
index fb7543368207a2bc47aeb54420662ec98b4c7082..be325c829632d861857f95696be39e7da1c1a419 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -150,9 +150,12 @@ class Post < ActiveRecord::Base
     post = if user
              user.find_visible_shareable_by_id(Post, id, :key => key)
            else
-             Post.where(key => id, :public => true).includes(:author, :comments => :author).first
+             Post.where(key => id).includes(:author, :comments => :author).first
            end
 
+    # is that a private post?
+    raise(Diaspora::NonPublic) unless user || post.public?
+
     post || raise(ActiveRecord::RecordNotFound.new("could not find a post with id #{id}"))
   end
 end
diff --git a/app/views/errors/not_public.html.haml b/app/views/errors/not_public.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..3f6abf9f7fe8ce4a9bba96a4e120eabb876b59eb
--- /dev/null
+++ b/app/views/errors/not_public.html.haml
@@ -0,0 +1,14 @@
+-# Copyright (c) 2010-2012, Diaspora Inc. This file is
+-# licensed under the Affero General Public License version 3 or later. See
+-# the COPYRIGHT file.
+
+- content_for :head do
+  = stylesheet_link_tag :error_pages, :media => 'all'
+
+#big-number.transparent
+  404
+
+#content
+  = t('error_messages.post_not_public')
+  %br
+  = t('error_messages.login_try_again', :login_link => new_user_session_path).html_safe
diff --git a/config/environment.rb b/config/environment.rb
index 7bd05b16f44ad8b8e1a9e5ababc64a7f5b8ebe87..fd26053a804b4e31febf078beb674b6f3a10f4dd 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -13,6 +13,8 @@ end
 
 # Load the rails application
 require File.expand_path('../application', __FILE__)
+require File.join(Rails.root, "lib", "exceptions")
+
 Haml::Template.options[:format] = :html5
 Haml::Template.options[:escape_html] = true
 
@@ -22,7 +24,7 @@ USERNAME_BLACKLIST = ['admin', 'administrator', 'hostmaster', 'info', 'postmaste
 
 # Initialize the rails application
 Diaspora::Application.initialize!
-require File.join(Rails.root, 'lib/federation_logger')
+require File.join(Rails.root, 'lib', 'federation_logger')
 
 # allow token auth only for posting activitystream photos
 module Devise
diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml
index 2077d33b118b3f8099269930a51bc708887e870c..00d79a704ca8657be1d5742a35985feb31ec6474 100644
--- a/config/locales/diaspora/en.yml
+++ b/config/locales/diaspora/en.yml
@@ -79,6 +79,8 @@ en:
     helper:
       invalid_fields: "Invalid Fields"
       correct_the_following_errors_and_try_again: "Correct the following errors and try again."
+    post_not_public: "The post you are trying to view is not public!"
+    login_try_again: "Please <a href='%{login_link}'>login</a> and try again."
 
   admins:
     admin_bar:
diff --git a/features/logged_out_browsing.feature b/features/logged_out_browsing.feature
index c0f26a08d54dd648b8bfc9be72dfc264d4863ec2..c49a98441a291c6ba58aeeae6915f1545be63ef6 100644
--- a/features/logged_out_browsing.feature
+++ b/features/logged_out_browsing.feature
@@ -21,3 +21,9 @@ Feature: Browsing Diaspora as a logged out user
     Scenario: Visiting a post show page
       When I view "bob@bob.bob"'s first post
       Then I should see "public stuff" within "body"
+
+    Scenario: Visiting a non-public post
+      Given "bob@bob.bob" has a non public post with text "my darkest secrets"
+      When I open the show page of the "my darkest secrets" post
+      Then I should see the "post not public" message
+      And I should not see "my darkest secrets"
diff --git a/features/step_definitions/message_steps.rb b/features/step_definitions/message_steps.rb
index 004acffca73f8900294b21d4584e9a7e7678c5d2..4a7a06d5b07b071ff305611d0d7eb09220e34640 100644
--- a/features/step_definitions/message_steps.rb
+++ b/features/step_definitions/message_steps.rb
@@ -9,6 +9,8 @@ Then /^I should see the "(.*)" message$/ do |message|
              I18n.translate('profiles.edit.you_are_safe_for_work')
            when 'you are nsfw'
              I18n.translate('profiles.edit.you_are_nsfw')
+           when 'post not public'
+             I18n.translate('error_messages.post_not_public')
            else
              raise "muriel, you don't have that message key, add one here"
            end
diff --git a/features/step_definitions/new_hotness/trumpeter_steps.rb b/features/step_definitions/new_hotness/trumpeter_steps.rb
index dcf3f7f8daffe7951c147cb28c5c27c454c705e3..c7672cdec55dc774b3c0a0cac9f0c8992145ff44 100644
--- a/features/step_definitions/new_hotness/trumpeter_steps.rb
+++ b/features/step_definitions/new_hotness/trumpeter_steps.rb
@@ -32,6 +32,10 @@ def go_to_framer
   find(".next").click()
 end
 
+def go_to_post_by_text post_text
+  visit post_path_by_content(post_text)
+end
+
 def finalize_frame
   find(".done").click()
 end
@@ -177,3 +181,7 @@ end
 When /^I go back to the composer$/ do
   find(".back").click()
 end
+
+When /^I open the show page of the "([^"]*)" post$/ do |post_text|
+  go_to_post_by_text post_text
+end
diff --git a/features/support/paths.rb b/features/support/paths.rb
index 4320fb2eb741bec0279dafa49fb24ecbe5674ce0..6cff77adfc15693087141a2f6d3ebaa3aea46f39 100644
--- a/features/support/paths.rb
+++ b/features/support/paths.rb
@@ -41,6 +41,11 @@ module NavigationHelpers
   def login_page
     path_to "the new user session page"
   end
+
+  def post_path_by_content text
+    p = Post.find_by_text(text)
+    post_path(p)
+  end
 end
 
 World(NavigationHelpers)
diff --git a/lib/exceptions.rb b/lib/exceptions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c278f72ebc457999d2492f25fd68e46e9b7fecf1
--- /dev/null
+++ b/lib/exceptions.rb
@@ -0,0 +1,8 @@
+# Copyright (c) 2010-2012, Diaspora Inc. This file is
+# licensed under the Affero General Public License version 3 or later. See
+# the COPYRIGHT file.
+
+module Diaspora
+  class NonPublic < StandardError
+  end
+end
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index ad74d2d9a60e71d689b7c643ab087231e944df85..50cff7b588fcb3db362fe3193095e892d8921694 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -56,7 +56,9 @@ describe PostsController do
       end
 
       it '404 if the post is missing' do
-        expect { get :show, :id => 1234567 }.to raise_error(ActiveRecord::RecordNotFound)
+        expect {
+          get :show, :id => 1234567
+        }.to raise_error(ActiveRecord::RecordNotFound)
       end
     end
 
@@ -85,7 +87,8 @@ describe PostsController do
 
       it 'does not show a private post' do
         status = alice.post(:status_message, :text => "hello", :public => false, :to => 'all')
-        expect { get :show, :id => status.id }.to raise_error(ActiveRecord::RecordNotFound)
+        get :show, :id => status.id
+        response.status.should == 404
       end
 
       # We want to be using guids from now on for this post route, but do not want to break
@@ -97,20 +100,26 @@ describe PostsController do
         end
 
         it 'assumes guids less than 8 chars are ids and not guids' do
-          Post.should_receive(:where).with(hash_including(:id => @status.id.to_s)).and_return(Post)
+          p = Post.where(:id => @status.id.to_s)
+          Post.should_receive(:where)
+              .with(hash_including(:id => @status.id.to_s))
+              .and_return(p)
           get :show, :id => @status.id
           response.should be_success
         end
 
         it 'assumes guids more than (or equal to) 8 chars are actually guids' do
-          Post.should_receive(:where).with(hash_including(:guid => @status.guid)).and_return(Post)
+          p = Post.where(:guid => @status.guid)
+          Post.should_receive(:where)
+              .with(hash_including(:guid => @status.guid))
+              .and_return(p)
           get :show, :id => @status.guid
           response.should be_success
         end
       end
     end
   end
-  
+
   describe 'iframe' do
     it 'contains an iframe' do
       get :iframe, :id => @message.id
@@ -126,7 +135,8 @@ describe PostsController do
     end
 
     it 'returns a 404 response when the post is not found' do
-      expect { get :oembed, :url => "/posts/#{@message.id}" }.to raise_error(ActiveRecord::RecordNotFound)
+      get :oembed, :url => "/posts/#{@message.id}"
+      response.status.should == 404
     end
   end