67

I'd like to be able to remotely connect to a Java service that has JMX exposed, however it is blocked by a firewall. I have tried to use ssh local port forwarding, however the connection fails. Looking at wireshark, it appears that when you try to connect with jconsole, it wants to connect via some ephemeral ports after connecting to port 9999, which are blocked by the firewall.

Is there any way to make jconsole only connect through 9999 or use a proxy? Is this article still the best solution? Or, am I missing something?

Community
  • 1
  • 1
blockcipher
  • 1,984
  • 4
  • 20
  • 32

4 Answers4

118

There's an even nicer way to do this using an SSH socks tunnel, since JConsole supports SOCKS:

  1. Create the SSH socks proxy locally on some free port (e.g. 7777):

    ssh -fN -D 7777 user@firewalled-host

  2. Run JConsole by specifying the SOCKS proxy (e.g. localhost:7777) and the address for the JMX server (e.g. localhost:2147)

    jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=7777 service:jmx:rmi:///jndi/rmi://localhost:2147/jmxrmi -J-DsocksNonProxyHosts=

As mentioned in one of the answers below, from JDK 8u60+ you also need to have the -J-DsocksNonProxyHosts= option in order to get it working.

Bogdan
  • 1,689
  • 3
  • 15
  • 16
  • If you have a jvm running on a remote host only available over ssh, if you can restart that jvm and modify its remote management options, and you just want to connect your local jconsole to this remote jvm, this is the way to go. It worked for me. Many thanks. – yohann.martineau Nov 29 '13 at 10:33
  • 1
    For part 1 add the `-N` flag if you don't need a remote SSH session (just the port forwarding), ie: `ssh -N -D 7777 user@host` – Wes Johnson Apr 17 '15 at 16:58
  • Thank you - I tried many different methods of trying to get remote JMX connectivity to work and this is the only way that worked for me. – Junho Park Oct 05 '15 at 19:46
  • As mentioned in one of the other answers, in newer versions of Java (>=8u60?), you also need to add `-J-DsocksNonProxyHosts=` – Jakub Kukul Dec 05 '16 at 13:21
  • can confirm this works with visualvm as well, excellent answer – Shanmu Dec 30 '16 at 14:27
  • +1 For the record, this works great when trying to connect to a JVM that's running in a docker container. In that case, I could not get a standard ssh tunnel (using `-L ...`) to work, probably because I could not match up with the interface on which JMX was listening (`-Djava.rmi.server.hostname='localhost'`), but a SOCKS proxy, set up correctly, was the ticket. – Peter Dec 23 '17 at 21:56
  • I was connecting to an aws server and this was the only solution that worked. – chubbsondubs Feb 03 '18 at 15:49
  • 1
    Can you please let me know where "localhost:2147" comes from in your example? – JJ Roman Aug 31 '20 at 10:14
  • It is the address where your JVM binds the JMX server where JConsole connects to. This is configured with the JVM startup flags. It may differ in your case. – Bogdan Sep 07 '20 at 16:57
  • This worked like a charm, thanks! @JJRoman, this is the number you assign to com.sun.management.jmxremote.port and com.sun.management.jmxremote.rmi.port in the remote JVM. – Kuro Kurosaka Jan 08 '21 at 22:43
62

With almost all current JDK versions (7u25 or later) it's now possible to use JConsole and Visual JVM over SSH quite easily (because now you can bind JMX to single port).

I use the following JVM parameters

-Dcom.sun.management.jmxremote.port=8090
-Dcom.sun.management.jmxremote.rmi.port=8090
-Djava.rmi.server.hostname=127.0.0.1
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Then I launch SSH connection

ssh my.javaserver.domain -L 8090:127.0.0.1:8090

After I can connect from JConsole

Remote Process: -> localhost:8090

And Java Visual VM

Right Click on Local -> Add JMX Connection -> localhost:8090

Community
  • 1
  • 1
Boris Treukhov
  • 16,478
  • 9
  • 66
  • 86
  • 1
    After trying so many methods, your answer really worked! Thanks much – teymourlouie Oct 27 '15 at 08:37
  • 1
    Thank you so much! I tried every other method I found and this is the only one which worked. – John A Jan 12 '16 at 17:19
  • 4
    Awesome, right what I was looking for. Here are my personal observations when used with VisualVM: `java.rmi.server.hostname=localhost` (instead of IP address) also works fine .... also: properties `com.sun.management.jmxremote.port` and `com.sun.management.jmxremote.rmi.port` must have **identical** values. – Abdull Jan 15 '16 at 16:01
  • 1
    wwwooooooooooohhhhhhaaaaa after 6 hours. OMG. Its like you saved the world my dear friend!!!! – Aadam May 23 '16 at 21:29
  • Works for me, JDK1.8.0_60. Thanks a lot. – zenbeni Jun 06 '16 at 09:43
  • Yep, this worked. Ubuntu 16.04, Tomcat7, Java8, through Vagrant VirtualBox no less. Thanks! – ghukill Jun 13 '17 at 14:14
  • the `-Djava.rmi.server.hostname` was the key part that the the accepted answer is missing. – wrschneider Oct 03 '19 at 17:37
40

Is there any way to make jconsole only connect through 9999 or use a proxy? Is this article still the best solution? Or, am I missing something?

Yes, that article is about right.

When you specify the JMX port on your server (-Dcom.sun.management.jmxremote.port=####), you are actually specifying just the registry-port for the application. When you connect it provides an additional server-port that the jconsole actually does all of its work with. To get forwarded to work, you need to know both the registry and server ports.

Something like the following should work to run your application with both the registry and server ports set to 8000. See here for more details.

-Dcom.sun.management.jmxremote.port=8000
-Dcom.sun.management.jmxremote.rmi.port=8000
-Djava.rmi.server.hostname=127.0.0.1

As an aside, my SimpleJMX library allows you to set both ports easily and you can set them both to be the same port.

So, once you know both the port(s) you need to forward, you can set up your ssh command. For example, if you configure the registry and server ports as 8000, you would do:

ssh -L 8000:localhost:8000 remote-host

This creates a local port 8000 which forwards to localhost:8000 on the remote-host. You can specify multiple -L arguments if you need to forward multiple ports. Then you can connect your jconsole to localhost:8000 and it will connect to the remote-host appropriately.

Also, if your server has multiple interfaces, you may need to set the java.rmi.server.hostname variable to bind to the right interface.

-Djava.rmi.server.hostname=10.1.2.3
Gray
  • 108,756
  • 21
  • 270
  • 333
  • O.K. Thanks. I was hoping this wasn't the answer because I really didn't want to write additional code just to monitor a service that we may have many instances of. However, your answer very clearly describes what's going on, so that helps. – blockcipher Feb 26 '13 at 16:57
  • If you are writing your own JMX code @blockcipher, you really should take a look at my SimpleJMX library. It really makes JMX pretty damn easy to code. http://256.com/sources/simplejmx/ – Gray Feb 26 '13 at 17:00
  • I tried lots of stuff, it only worked after I opened the doors, connected directly and used this specific form of the url, "service:jmx:...". Thanks! – dividebyzero Jun 28 '15 at 04:29
13

Continuing the SSH socks method, with newer java versions (around 8u66) you also need to set socksNonProxyHosts empty resulting in:

jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=7777 -J-DsocksNonProxyHosts=
Tristan Hill
  • 171
  • 1
  • 3