diff --git src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
index c6a22a2..ad837ca 100644
--- src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
+++ src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
@@ -5,8 +5,10 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
@@ -19,6 +21,7 @@ import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
 import org.openstreetmap.josm.data.cache.CacheEntry;
 import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
@@ -41,30 +44,104 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
      * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS
      */
     public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25);
+
+    /**
+     * Limit definition for per host concurrent connections
+     */
+    public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6);
+
     /**
      * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS
      * and for TMS imagery
      */
-    private static ThreadPoolExecutor DOWNLOAD_JOB_DISPATCHER = new ThreadPoolExecutor(
-            THREAD_LIMIT.get().intValue(), // keep the thread number constant
-            THREAD_LIMIT.get().intValue(), // do not this number of threads
-            30, // keepalive for thread
-            TimeUnit.SECONDS,
-            // make queue of LIFO type - so recently requested tiles will be loaded first (assuming that these are which user is waiting to see)
-            new LinkedBlockingDeque<Runnable>(5) {
-                /* keep the queue size fairly small, we do not want to
-                 download a lot of tiles, that user is not seeing anyway */
-                @Override
-                public boolean offer(Runnable t) {
-                    return super.offerFirst(t);
+
+    private static class LIFOQueue extends LinkedBlockingDeque<Runnable> {
+        public LIFOQueue(int capacity) {
+            super(capacity);
+        }
+
+        private final static Semaphore getSemaphore(Runnable r) {
+            if (!(r instanceof TMSCachedTileLoaderJob))
+                return null;
+            TMSCachedTileLoaderJob cachedJob = (TMSCachedTileLoaderJob) r;
+            Semaphore limit = HOST_LIMITS.get(cachedJob.getUrl().getHost());
+            if (limit == null) {
+                synchronized(HOST_LIMITS) {
+                    limit = HOST_LIMITS.get(cachedJob.getUrl().getHost());
+                    if (limit == null) {
+                        limit = new Semaphore(HOST_LIMIT.get().intValue());
+                        HOST_LIMITS.put(cachedJob.getUrl().getHost(), limit);
+                    }
                 }
+            }
+            return limit;
+        }
 
-                @Override
-                public Runnable remove() {
-                    return super.removeFirst();
+        private boolean acquireSemaphore(Runnable r) {
+            boolean ret = true;
+            Semaphore limit = getSemaphore(r);
+            if (limit != null) {
+                ret = limit.tryAcquire();
+                if (!ret) {
+                    Main.debug("rejecting job because of per host limit");
                 }
             }
-            );
+            return ret;
+
+        }
+        @Override
+        public boolean offer(Runnable t) {
+            return acquireSemaphore(t) && super.offerFirst(t);
+        }
+
+        private Runnable releaseSemaphore(Runnable r) {
+            Semaphore limit = getSemaphore(r);
+            if (limit != null)
+                limit.release();
+            return r;
+        }
+
+        @Override
+        public Runnable remove() {
+            return releaseSemaphore(super.removeFirst());
+        }
+
+        @Override
+        public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
+            return releaseSemaphore(super.poll(timeout, unit));
+        }
+
+        @Override
+        public Runnable take() throws InterruptedException {
+            return releaseSemaphore(super.take());
+        }
+    }
+
+    private static Map<String, Semaphore> HOST_LIMITS = new ConcurrentHashMap<>();
+
+    private static ThreadPoolExecutor DOWNLOAD_JOB_DISPATCHER = getThreadPoolExecutor();
+
+    private static ThreadPoolExecutor getThreadPoolExecutor() {
+        return new ThreadPoolExecutor(
+                THREAD_LIMIT.get().intValue(), // keep the thread number constant
+                THREAD_LIMIT.get().intValue(), // do not this number of threads
+                30, // keepalive for thread
+                TimeUnit.SECONDS,
+                // make queue of LIFO type - so recently requested tiles will be loaded first (assuming that these are which user is waiting to see)
+                new LIFOQueue(5)
+                    /* keep the queue size fairly small, we do not want to
+                     download a lot of tiles, that user is not seeing anyway */
+                );
+    }
+
+    /**
+     * Reconfigures download dispatcher using current values of THREAD_LIMIT and HOST_LIMIT
+     */
+    public static final void reconfigureDownloadDispatcher() {
+        HOST_LIMITS = new ConcurrentHashMap<>();
+        DOWNLOAD_JOB_DISPATCHER = getThreadPoolExecutor();
+    }
+
 
     /**
      * Constructor for creating a job, to get a specific tile from cache
diff --git src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java
index 75ce084..0a7b89d 100644
--- src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java
+++ src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java
@@ -32,6 +32,7 @@ public class TMSSettingsPanel extends JPanel {
     private final JosmTextField tilecacheDir = new JosmTextField();
     private final JSpinner maxElementsOnDisk;
     private final JSpinner maxConcurrentDownloads;
+    private final JSpinner maxDownloadsPerHost;
 
 
     /**
@@ -43,6 +44,7 @@ public class TMSSettingsPanel extends JPanel {
         maxZoomLvl = new JSpinner(new SpinnerNumberModel(TMSLayer.DEFAULT_MAX_ZOOM, TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1));
         maxElementsOnDisk = new JSpinner(new SpinnerNumberModel(TMSCachedTileLoader.MAX_OBJECTS_ON_DISK.get().intValue(), 0, Integer.MAX_VALUE, 1));
         maxConcurrentDownloads = new JSpinner(new SpinnerNumberModel(TMSCachedTileLoaderJob.THREAD_LIMIT.get().intValue(), 0, Integer.MAX_VALUE, 1));
+        maxDownloadsPerHost = new JSpinner(new SpinnerNumberModel(TMSCachedTileLoaderJob.HOST_LIMIT.get().intValue(), 0, Integer.MAX_VALUE, 1));
 
         add(new JLabel(tr("Auto zoom by default: ")), GBC.std());
         add(GBC.glue(5, 0), GBC.std());
@@ -72,6 +74,11 @@ public class TMSSettingsPanel extends JPanel {
         add(GBC.glue(5, 0), GBC.std());
         add(maxConcurrentDownloads, GBC.eol());
 
+        add(new JLabel(tr("Maximum concurrent downloads per host: ")), GBC.std());
+        add(GBC.glue(5, 0), GBC.std());
+        add(maxDownloadsPerHost, GBC.eol());
+
+
         add(new JLabel(tr("Maximum elements in disk cache: ")), GBC.std());
         add(GBC.glue(5, 0), GBC.std());
         add(this.maxElementsOnDisk, GBC.eol());
@@ -90,6 +97,7 @@ public class TMSSettingsPanel extends JPanel {
         this.tilecacheDir.setText(TMSLayer.PROP_TILECACHE_DIR.get());
         this.maxElementsOnDisk.setValue(TMSCachedTileLoader.MAX_OBJECTS_ON_DISK.get());
         this.maxConcurrentDownloads.setValue(TMSCachedTileLoaderJob.THREAD_LIMIT.get());
+        this.maxDownloadsPerHost.setValue(TMSCachedTileLoaderJob.HOST_LIMIT.get());
     }
 
     /**
@@ -110,10 +118,9 @@ public class TMSSettingsPanel extends JPanel {
 
         TMSCachedTileLoader.MAX_OBJECTS_ON_DISK.put((Integer) this.maxElementsOnDisk.getValue());
 
-        if (!TMSCachedTileLoaderJob.THREAD_LIMIT.get().equals(this.maxConcurrentDownloads.getValue())) {
-            restartRequired = true;
-            TMSCachedTileLoaderJob.THREAD_LIMIT.put((Integer) this.maxConcurrentDownloads.getValue());
-        }
+        TMSCachedTileLoaderJob.THREAD_LIMIT.put((Integer) this.maxConcurrentDownloads.getValue());
+        TMSCachedTileLoaderJob.HOST_LIMIT.put((Integer) this.maxDownloadsPerHost.getValue());
+        TMSCachedTileLoaderJob.reconfigureDownloadDispatcher();
 
         if (!TMSLayer.PROP_TILECACHE_DIR.get().equals(this.tilecacheDir.getText())) {
             restartRequired = true;
