Self-hosting Zenphoto on Windows 7 (IIS7, PHP & MySQL)

I really like ZenPhoto – it’s a solid photo gallery with an easy point and click web admin GUI.
The main thing I dig is that i can point it at my main photos folder on my hard drive (via a quick symbolic link) and it goes to town dynamically publishing whatever I drop there without any other fiddling… that’s photo sharing nirvana if you ask me. [Update: 25 Oct 2010] After running the gallery for a couple days I’d have to say Apache did a better job at popping the pages back than what I’ve got setup under IIS so far… maybe Apache just deals with these more CGI oriented modules better than IIS can for some fundamental reason… I do have PHP “FastCGI” enabled for IIS… any performance tips would be greatly appreciated 🙂 I’ll have to go find some trace tools to see where it’s spending most of its time.  I guess it could still be caching images since ZenPhoto pulls a new random image for the album cover each time you refresh the page and I did wipe the cache when I migrated over to IIS. [Update: 29 Oct 2010] Setting Admin Options > Image subtab > Full image protection = Unprotected – yields a noticeable speed boost presumably because it skips a bunch of file I/O… now it feels back on par with what I was seeing in Apache… unfortunately, I just don’t remember how I had that setting under Apache. Installs:

  • I installed them all to c:Program Files because that’s my speedy SSD and I want this site to be as performant as possible
    • then i simply SymLink my main photos folder (on a RAID1 volume elsewhere) over the top of c:Program Fileszenphotoalbums
    • here’s an awesome SymLink utility for Windows Explorer!!
  • IIS – I’m on Win7 so it’s IIS7 – Apache’s cool and all but i saw a note somewhere that gave me the impression that on Windows, IIS + PHP via FastCGI module is  going to be more performant than Apache… otherwise, I did previously run it all on Apache just fine via the nifty “XAMPP” stack that installs everything for you in minutes and it “just works” which was honestly much less trouble than getting it all to hang together under IIS7 myself.
  • PHP – there’s a specific Windows/IIS “Fast CGI” version (current version: 5.3.3) (see this for Thread Safe vs Non Thread Safe binaries, non thread safe + IIS FastCGI is most performant)
  • MySQL – and their WorkBench tool is handy (current version: 5.1.51)
    • there’s a lot of environmental tuning questions during the install wizard but i mostly selected default settings
    • I chose to go with a MySQL_Data subfolder for the datafiles
    • configure for TCP/IP access (i don’t yet know how to configure PHP to connect to MySQL over named pipes)
  • ZenPhoto – just an unzip (current version: 1.3.1.2)
  • zpGallerific theme (current version: 1.0)

FIREWALL!!! turn it completely off to begin with so you know whether it’s your main problem or not

  • I had to add these two rules to BitDefender
  • image
  • THE ORDER OF THE RULES MATTERS… MOVE THESE TO THE VERY TOP of the list with the arrow buttons!!
  • helpful: http://forum.bitdefender.com/index.php?showtopic=12764
  • nutshell: to see what’s blocked “Increase Verbosity” and “Show Log” on Activity tab 

**Folder Permissions:
**

  • grant IUSR full permissions to root zenphoto folder (IIS_IUSRS group did NOT work)
    • it was also necessary on the true target of the symlinked albums folder
  • something happened on my win7 box where my albums folder was no longer accessible to zenPhoto/PHP… maybe a Windows Update closed a security loophole or something…

IIS Tweaks:

  • Enable 32bit PHP under IIS on 64bit Windows
    • install IIS6.0 script compatibily
    • cscript %SYSTEMDRIVE%inetpubadminscriptsadsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnW
  • MOD_REWRITE
    • once I got everything fired up I realized that it’d be nice to support my old URLs that I’ve mailed out to everybody already
    • interesting thing was, Apache was doing something cool I didn’t realize… it was mod_rewrite’ing my php urls for me so they looked like pretty folders
    • actually zenphoto was kicking out the pretty urls and mod_rewrite was translating them back into /index.php?album=blah format behind the covers
    • IIS doesn’t do that right out of the box but they have a nice free URL Rewrite module you can drop in to do this very same thing (v2.0 currently)
    • you have to restart IIS Manager GUI after you install to see the “URL Rewrite” icon under the “IIS” section of your web site
    • it has a good wizard for the easy stuff which is all I needed to map “photos/(.*)/” to “photos/index.php?album={R:1}”
    • also under conditionals, input: {REQUEST_FILENAME} => “Is Not a File” & “Is Not a Folder” was crucial to allow the real URLs for direct downloading of images and such to continue working
    • Here’ are all the rewrite rules I needed to apply:
web.config
<div style="overflow: auto; background: #ddd; max-height: 200px">
  <ol style="background: #f4f4f4; padding-bottom: 0px; padding-top: 0px; padding-left: 5px; margin: 0px 0px 0px 2.5em; padding-right: 0px">
    <li>
      <?xml version="1.0"?>
    </li>
    <li>
      <configuration>
    </li>
    <li>
      &#160;&#160;&#160; <system.webServer>
    </li>
    <li>
      &#160;
    </li>
    <li>
      &#160;&#160;&#160; <rewrite>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <rules>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <clear/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <rule name="RewriteUserFriendlyURL6" enabled="true" stopProcessing="true">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <match url="^page/search/archive/(.*)$"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </conditions>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <action type="Rewrite" url="index.php?p=search&date={R:1}"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </rule>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <rule name="RewriteUserFriendlyURL5" enabled="true" stopProcessing="true">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <match url="^page/([0-9]+)/?$"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </conditions>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <action type="Rewrite" url="index.php?page={R:1}"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </rule>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <rule name="RewriteUserFriendlyURL4" enabled="true" patternSyntax="ECMAScript" stopProcessing="true">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <match url="^page/(.*?)$"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </conditions>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <action type="Rewrite" url="index.php?p={R:1}" appendQueryString="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </rule>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <rule name="RewriteUserFriendlyURL1" enabled="true" stopProcessing="true">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <match url="^(.*?)/?$"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </conditions>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <action type="Rewrite" url="index.php?album={R:1}" appendQueryString="false"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </rule>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; </rules>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160; </rewrite>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160; <directoryBrowse enabled="true"/>
    </li>
    <li>
      &#160;&#160;&#160; </system.webServer>
    </li>
    <li>
      &#160;
    </li>
    <li>
      &#160; <system.web>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160; <compilation targetFramework="4.0" debug="true"/>
    </li>
    <li>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160; <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/>
    </li>
    <li>
      &#160; </system.web>
    </li>
    <li>
      &#160;
    </li>
    <li>
      </configuration>
    </li>
  </ol>
</div></p>

Create Self-Signed Cert IIS7

PHP Setup:

  • Map an IIS virtual directory to your zenphoto root
  • browse to
  • it’ll probably bark about a couple settings you have to make manually… no biggie hopefully
  • you’ll have to reset “World Wide Web Publishing Service” to refresh any PHP settings it tells you to twiddle
  • I had to leave file/folder permissions as “loose (0777)”… all of the stricter settings blocked zenphoto subfolder permissions
  • MySQL settings:
    • root login & password
    • 127.0.0.1:3306 (localhost did NOT work!?!)
    • database name (“zenphoto”)
    • table prefix = blank (i preferred to go with a separate database w/o table prefixes)
    • it’ll create the zenphoto database for you with a simple click once you get a successful login to MySQL server working
    • *GO* 🙂
  • i went ahead and let it delete the “zp-coresetup*.php” files
  • set admin username & password
  • You’re in!

ZenPhoto admin page settings:

  • just unzip zpGallerific folder into the zenphotothemes folder
  • Theme’s tab – activate zpGallerific
  • Options tab
    • general subtab – Time zone = Europe/Berlin
    • gallery subtab
      • title = The Andersons
      • description = {blank} (set Gallerific subtitle next)
      • sorty by = filename – descending (works for me because i name all folders “yyyy-mm-dd {description}”)
    • image subtab – Full image protection = Unprotected (as long as you don’t really care who gets access, this yields a MAJOR speed boost for page rendering times)
    • theme subtab
      • Albums per page = 9
      • Color = Blue
      • Tagline = Cassidy, Anne, BJ & Friends
comments powered by Disqus